deps: Stop vendoring WebRender (#31212)

The new strategy for dependencies with upstream in Gecko is to manage
them in separate repositories, which will more easily allow rebasing our
changes on top of newer Gecko work.
This commit is contained in:
Martin Robinson 2024-01-30 09:10:13 +01:00 committed by GitHub
parent 9b6c473695
commit 7f0d0830e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1232 changed files with 11 additions and 183095 deletions

6
Cargo.lock generated
View file

@ -4195,6 +4195,7 @@ dependencies = [
[[package]] [[package]]
name = "peek-poke" name = "peek-poke"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/servo/webrender?rev=f91b68a61#f91b68a616377da0f3f8858f3cead3e47da4acdd"
dependencies = [ dependencies = [
"euclid", "euclid",
"peek-poke-derive", "peek-poke-derive",
@ -4203,6 +4204,7 @@ dependencies = [
[[package]] [[package]]
name = "peek-poke-derive" name = "peek-poke-derive"
version = "0.2.1" version = "0.2.1"
source = "git+https://github.com/servo/webrender?rev=f91b68a61#f91b68a616377da0f3f8858f3cead3e47da4acdd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -6811,6 +6813,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
[[package]] [[package]]
name = "webrender" name = "webrender"
version = "0.61.0" version = "0.61.0"
source = "git+https://github.com/servo/webrender?rev=f91b68a61#f91b68a616377da0f3f8858f3cead3e47da4acdd"
dependencies = [ dependencies = [
"bincode", "bincode",
"bitflags 1.3.2", "bitflags 1.3.2",
@ -6850,6 +6853,7 @@ dependencies = [
[[package]] [[package]]
name = "webrender_api" name = "webrender_api"
version = "0.61.0" version = "0.61.0"
source = "git+https://github.com/servo/webrender?rev=f91b68a61#f91b68a616377da0f3f8858f3cead3e47da4acdd"
dependencies = [ dependencies = [
"app_units", "app_units",
"bitflags 1.3.2", "bitflags 1.3.2",
@ -6871,6 +6875,7 @@ dependencies = [
[[package]] [[package]]
name = "webrender_build" name = "webrender_build"
version = "0.0.1" version = "0.0.1"
source = "git+https://github.com/servo/webrender?rev=f91b68a61#f91b68a616377da0f3f8858f3cead3e47da4acdd"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"lazy_static", "lazy_static",
@ -7328,6 +7333,7 @@ dependencies = [
[[package]] [[package]]
name = "wr_malloc_size_of" name = "wr_malloc_size_of"
version = "0.0.1" version = "0.0.1"
source = "git+https://github.com/servo/webrender?rev=f91b68a61#f91b68a616377da0f3f8858f3cead3e47da4acdd"
dependencies = [ dependencies = [
"app_units", "app_units",
"euclid", "euclid",

View file

@ -111,8 +111,8 @@ uuid = { version = "1.7.0", features = ["v4"] }
webdriver = "0.49.0" webdriver = "0.49.0"
webpki = "0.22" webpki = "0.22"
webpki-roots = "0.25" webpki-roots = "0.25"
webrender = { git = "https://github.com/servo/webrender", features = ["capture"] } webrender = { git = "https://github.com/servo/webrender", rev = "f91b68a61", features = ["capture"] }
webrender_api = { git = "https://github.com/servo/webrender" } webrender_api = { git = "https://github.com/servo/webrender", rev = "f91b68a61" }
webrender_traits = { path = "components/shared/webrender" } webrender_traits = { path = "components/shared/webrender" }
wgpu-core = "0.18" wgpu-core = "0.18"
wgpu-types = "0.18" wgpu-types = "0.18"
@ -141,9 +141,3 @@ debug-assertions = false
# #
# [patch."https://github.com/servo/<repository>"] # [patch."https://github.com/servo/<repository>"]
# <crate> = { path = "/path/to/local/checkout" } # <crate> = { path = "/path/to/local/checkout" }
# This is required because we want all dependencies that use WebRender to
# use our vendored version.
[patch."https://github.com/servo/webrender"]
webrender = { path = "third_party/webrender/webrender" }
webrender_api = { path = "third_party/webrender/webrender_api" }

View file

@ -15,4 +15,4 @@ app_units = { workspace = true }
euclid = { workspace = true } euclid = { workspace = true }
malloc_size_of = { path = "../malloc_size_of" } malloc_size_of = { path = "../malloc_size_of" }
malloc_size_of_derive = { workspace = true } malloc_size_of_derive = { workspace = true }
webrender_api = { git = "https://github.com/servo/webrender" } webrender_api = { workspace = true }

View file

@ -38,4 +38,4 @@ servo_rand = { path = "../../rand" }
servo_url = { path = "../../url" } servo_url = { path = "../../url" }
url = { workspace = true } url = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }
webrender_api = { git = "https://github.com/servo/webrender" } webrender_api = { workspace = true }

View file

@ -34,4 +34,4 @@ servo_atoms = { path = "../../atoms" }
servo_url = { path = "../../url" } servo_url = { path = "../../url" }
style = { path = "../../style", features = ["servo"] } style = { path = "../../style", features = ["servo"] }
style_traits = { workspace = true } style_traits = { workspace = true }
webrender_api = { git = "https://github.com/servo/webrender" } webrender_api = { workspace = true }

View file

@ -1,27 +0,0 @@
target/
*~
*#
# WR internals
captures
wrench/json_frames
wrench/ron_frames
# Editors
*.swp
*.swo
# IntelliJ
.idea
*.iws
*.iml
# Gradle
.gradle
# VSCode
.vscode
.vs
# System
.fuse_hidden*

View file

@ -1,176 +0,0 @@
# This file specifies the configuration needed to test WebRender using the
# Taskcluster infrastructure. Most of this should be relatively
# self-explanatory; this file was originally generated by using the
# Taskcluster-GitHub integration quickstart tool which no longer exists.
#
# See https://community-tc.services.mozilla.com/docs
version: 1
policy:
pullRequests: public
# This file triggers a set of tasks; the ones targeting Linux are run in a docker
# container using docker-worker (which is a worker type provided by TaskCluster).
# The OS X ones are run in a custom worker type, for which we have worker
# instances configured and running.
tasks:
$if: 'tasks_for in ["github-push", "github-pull-request"]'
then:
$let:
should_run:
$if: 'tasks_for == "github-push"'
# for pushes, run on any branch but master
then: {$eval: 'event.ref != "refs/heads/master"'}
# for PRs, run for opened and synchronized events
else: {$eval: 'event.action in ["opened", "synchronize"]'}
repo_url:
$if: 'tasks_for == "github-push"'
then: ${event.repository.clone_url}
else: ${event.pull_request.head.repo.clone_url}
sha:
$if: 'tasks_for == "github-push"'
then: ${event.after}
else: ${event.pull_request.head.sha}
login: ${event.sender.login}
branch:
$if: 'tasks_for == "github-push"'
then:
$if: 'event.ref[:11] == "refs/heads/"'
then: ${event.ref[11:]}
else: ${event.ref}
else: ${event.pull_request.head.ref}
in:
$if: should_run
then:
# For the docker-worker tasks, the Docker image used
# (staktrace/webrender-test:debian-v6) was created using the Dockerfile in
# ci-scripts/docker-image.
#
# The docker image may need to be updated over time if the set of required
# packages increases. Note in particular that rust/cargo are not part of the
# docker image, and are re-installed using rustup on each CI run. This ensures
# the latest stable rust compiler is always used.
# CI runs will be triggered on opening PRs, updating PRs, and pushes to the
# repository.
- metadata:
name: Linux release tests
description: Runs release-mode WebRender CI stuff on a Linux TC worker
owner: noreply@mozilla.com
source: ${repo_url}
provisionerId: proj-webrender
workerType: ci-linux
deadline: {$fromNow: '1 day'}
payload:
maxRunTime: 7200
image: 'staktrace/webrender-test:debian-v6'
env:
RUST_BACKTRACE: 'full'
RUSTFLAGS: '--deny warnings'
command:
- /bin/bash
- '--login'
- '-c'
- >-
curl https://sh.rustup.rs -sSf | sh -s -- -y &&
source $HOME/.cargo/env &&
git clone ${repo_url} webrender && cd webrender &&
git checkout ${sha} &&
servo-tidy &&
ci-scripts/linux-release-tests.sh
routes:
- "index.project.webrender.ci.${login}.${branch}.linux-release"
- metadata:
name: Linux debug tests
description: Runs debug-mode WebRender CI stuff on a Linux TC worker
owner: noreply@mozilla.com
source: ${repo_url}
provisionerId: proj-webrender
workerType: ci-linux
deadline: {$fromNow: '1 day'}
payload:
maxRunTime: 7200
image: 'staktrace/webrender-test:debian-v6'
env:
RUST_BACKTRACE: 'full'
RUSTFLAGS: '--deny warnings'
command:
- /bin/bash
- '--login'
- '-c'
- >-
curl https://sh.rustup.rs -sSf | sh -s -- -y &&
source $HOME/.cargo/env &&
git clone ${repo_url} webrender && cd webrender &&
git checkout ${sha} &&
servo-tidy &&
ci-scripts/linux-debug-tests.sh
routes:
- "index.project.webrender.ci.${login}.${branch}.linux-debug"
# For the OS X jobs we use a pool of machines that we are managing, because
# Mozilla releng doesn't have any spare OS X machines for us at this time.
# Talk to :kats or :jrmuizel if you need more details about this. The machines
# are hooked up to taskcluster using taskcluster-worker; they use a workerpool
# of proj-webrender/ci-macos. They are set up with all the dependencies needed
# to build and test webrender, including Rust (currently 1.41.1), servo-tidy,
# mako, zlib, etc. Note that unlike the docker-worker used for Linux, these
# machines WILL persist state from one run to the next, so any cleanup needs
# to be handled explicitly.
# macOS tasks currently disabled, see bug 1639217.
# - metadata:
# name: OS X release tests
# description: Runs release-mode WebRender CI stuff on a OS X TC worker
# owner: noreply@mozilla.com
# source: ${repo_url}
# provisionerId: 'proj-webrender'
# workerType: 'ci-macos'
# deadline: {$fromNow: '1 day'}
# payload:
# maxRunTime: 3600
# command:
# - - /bin/bash
# - '--login'
# - '-vec'
# - |
# git clone ${repo_url} webrender
# cd webrender
# git checkout ${sha}
# source $HOME/servotidy-venv/bin/activate
# servo-tidy
# export RUST_BACKTRACE=full
# export RUSTFLAGS='--deny warnings'
# export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH"
# echo 'exec make -j1 "$@"' > $HOME/make # See #2638
# chmod +x $HOME/make
# export MAKE="$HOME/make"
# ci-scripts/macos-release-tests.sh
# routes:
# - "index.project.webrender.ci.${login}.${branch}.osx-release"
# - metadata:
# name: OS X debug tests
# description: Runs debug-mode WebRender CI stuff on a OS X TC worker
# owner: noreply@mozilla.com
# source: ${repo_url}
# provisionerId: 'proj-webrender'
# workerType: 'ci-macos'
# deadline: {$fromNow: '1 day'}
# payload:
# maxRunTime: 3600
# command:
# - - /bin/bash
# - '--login'
# - '-vec'
# - |
# git clone ${repo_url} webrender
# cd webrender
# git checkout ${sha}
# source $HOME/servotidy-venv/bin/activate
# servo-tidy
# export RUST_BACKTRACE=full
# export RUSTFLAGS='--deny warnings'
# export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH"
# echo 'exec make -j1 "$@"' > $HOME/make # See #2638
# chmod +x $HOME/make
# export MAKE="$HOME/make"
# ci-scripts/macos-debug-tests.sh
# routes:
# - "index.project.webrender.ci.${login}.${branch}.osx-debug"

1905
third_party/webrender/Cargo.lock generated vendored

File diff suppressed because it is too large Load diff

View file

@ -1,33 +0,0 @@
[workspace]
members = [
"examples",
"webrender",
"webrender_api",
"wrench",
"example-compositor/compositor",
"tileview",
]
[profile.release]
debug = true
panic = "abort"
[profile.dev]
panic = "abort"
# optimizing glsl more makes a big difference in swgl build times
[profile.dev.package.glsl]
opt-level = 2
[profile.release.package.glsl]
opt-level = 2
# Running wrench on android built with master cargo-apk results in a crash
# due to a mismatched version of android_glue (a dependency of winit).
# Override it to use a suitable version of android_glue.
# See https://github.com/rust-windowing/android-rs-glue/issues/239.
# This can be removed once a new version of android_glue is published to crates.io.
[patch.crates-io]
android_glue = { git = "https://github.com/rust-windowing/android-rs-glue.git", rev = "e3ac6edea5814e1faca0c31ea8fac6877cb929ea" }
# this is a version that fixes some incompatibilites with newer rust/aarch64
winit = { version = "0.19", git = "https://github.com/jrmuizel/winit", branch="wr" }

View file

@ -1,374 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View file

@ -1,53 +0,0 @@
# WebRender
[![Version](https://img.shields.io/crates/v/webrender.svg)](https://crates.io/crates/webrender)
WebRender is a GPU-based 2D rendering engine written in [Rust](https://www.rust-lang.org/). [Firefox](https://www.mozilla.org/firefox), the research web browser [Servo](https://github.com/servo/servo), and other GUI frameworks draw with it. It currently uses the OpenGL API internally.
Note that the canonical home for this code is in gfx/wr folder of the
mozilla-central repository at https://hg.mozilla.org/mozilla-central. The
Github repository at https://github.com/servo/webrender should be considered
a downstream mirror, although it contains additional metadata (such as Github
wiki pages) that do not exist in mozilla-central. Pull requests against the
Github repository are still being accepted, although once reviewed, they will
be landed on mozilla-central first and then mirrored back. If you are familiar
with the mozilla-central contribution workflow, filing bugs in
[Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Graphics%3A%20WebRender)
and submitting patches there would be preferred.
## Update as a Dependency
After updating shaders in WebRender, go to servo and:
* Go to the servo directory and do ./mach update-cargo -p webrender
* Create a pull request to servo
## Use WebRender with Servo
To use a local copy of WebRender with servo, go to your servo build directory and:
* Edit Cargo.toml
* Add at the end of the file:
```
[patch."https://github.com/servo/webrender"]
"webrender" = { path = "<path>/webrender" }
"webrender_api" = { path = "<path>/webrender_api" }
```
where `<path>` is the path to your local copy of WebRender.
* Build as normal
## Documentation
The Wiki has a [few pages](https://github.com/servo/webrender/wiki/) describing the internals and conventions of WebRender.
## Testing
Tests run using OSMesa to get consistent rendering across platforms.
Still there may be differences depending on font libraries on your system, for
example.
See [this gist](https://gist.github.com/finalfantasia/129cae811e02bf4551ac) for
how to make the text tests useful in Fedora, for example.

View file

@ -1,12 +0,0 @@
FROM debian:buster-20200422
# Debian 10 doesn't have openjdk-8, so add the Debian 9 repository, which contains it.
RUN sed s/buster/stretch/ /etc/apt/sources.list | tee /etc/apt/sources.list.d/stretch.list
COPY setup.sh /root
RUN cd /root && ./setup.sh
RUN useradd -d /home/worker -s /bin/bash -m worker
USER worker
WORKDIR /home/worker
CMD /bin/bash

View file

@ -1,44 +0,0 @@
#!/usr/bin/env bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace
test "$(whoami)" == 'root'
# Install stuff we need
apt-get -y update
apt-get install -y \
bison \
bzip2 \
cmake \
curl \
flex \
gcc \
git \
g++ \
libfontconfig1-dev \
libgl1-mesa-dev \
libx11-dev \
llvm-dev \
ninja-build \
openjdk-8-jdk \
pkg-config \
python \
python-mako \
python-pip \
python-setuptools \
python-voluptuous \
python-yaml \
python3-pip \
python3-mako \
software-properties-common \
clang
# Other stuff we need
pip install servo-tidy==0.3.0

View file

@ -1,18 +0,0 @@
[binaries]
llvm-config = '/builds/worker/fetches/clang/bin/llvm-config'
[properties]
# When linking `libOSMesa.dylib` Meson uses options provided by `llvm-config`.
# The binary for `llvm-config` in Firefox CI comes from a native Linux clang,
# which gives the link options for the Linux libLLVM-11.so in the Linux clang.
# However, we want to link against a native macOS clang's libLLVM.dylib, which
# we have available in a separate directory.
# Meson will still have -lLLVM-11 on the command line, but the linker will
# only warn that it has the wrong format (because it's not for macOS).
cpp_link_args = ['-L/builds/worker/fetches/clang-mac/clang/lib', '-lLLVM']
[host_machine]
system = 'darwin'
cpu_family = 'x86_64'
cpu = 'i686'
endian = 'little'

View file

@ -1,22 +0,0 @@
#!/usr/bin/env bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
# This file downloads and installs meson which is required for building
# osmesa-src, a dependency of wrench.
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace
MESON_VER=0.55.1
MESON_BASE_URL="https://github.com/mesonbuild/meson/releases/download"
curl -L ${MESON_BASE_URL}/${MESON_VER}/meson-${MESON_VER}.tar.gz -o meson.tar.gz
tar -xf meson.tar.gz
mv meson-${MESON_VER} meson
cd meson
ln -s meson.py meson

View file

@ -1,36 +0,0 @@
#!/usr/bin/env bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
# This must be run from the root webrender directory!
# Users may set the CARGOFLAGS environment variable to pass
# additional flags to cargo if desired.
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace
CARGOFLAGS=${CARGOFLAGS:-"--verbose"} # default to --verbose if not set
pushd webrender
cargo build ${CARGOFLAGS} --no-default-features
cargo build ${CARGOFLAGS} --no-default-features --features capture
cargo build ${CARGOFLAGS} --features capture,profiler
cargo build ${CARGOFLAGS} --features replay
popd
pushd wrench
cargo build ${CARGOFLAGS} --features env_logger
OPTIMIZED=0 python script/headless.py reftest
popd
pushd examples
cargo build ${CARGOFLAGS}
popd
cargo test ${CARGOFLAGS} \
--all --exclude compositor --exclude compositor-wayland \
--exclude compositor-windows --exclude glsl-to-cxx --exclude swgl

View file

@ -1,28 +0,0 @@
#!/usr/bin/env bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
# This must be run from the root webrender directory!
# Users may set the CARGOFLAGS environment variable to pass
# additional flags to cargo if desired.
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace
CARGOFLAGS=${CARGOFLAGS:-""} # default to empty if not set
pushd wrench
# Test that all shaders compile successfully.
python script/headless.py --precache test_init
python script/headless.py --precache --use-unoptimized-shaders test_init
python script/headless.py reftest
python script/headless.py rawtest
python script/headless.py test_invalidation
CXX=clang++ cargo run ${CARGOFLAGS} --release --features=software -- \
--software --headless reftest
popd

View file

@ -1,42 +0,0 @@
#!/usr/bin/env bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
# This must be run from the root webrender directory!
# Users may set the CARGOFLAGS environment variable to pass
# additional flags to cargo if desired.
# Note that this script is run in a special cross-compiling configuration,
# where CARGOTESTFLAGS includes `--no-run`, and the binaries produced by
# `cargo test` are run on a different machine. When making changes to this
# file, please ensure any such binaries produced by `cargo test` are not
# deleted, or they may not get run as expected.
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace
CARGOFLAGS=${CARGOFLAGS:-"--verbose"} # default to --verbose if not set
CARGOTESTFLAGS=${CARGOTESTFLAGS:-""}
pushd webrender
cargo check ${CARGOFLAGS} --no-default-features
cargo check ${CARGOFLAGS} --no-default-features --features capture
cargo check ${CARGOFLAGS} --features capture,profiler
cargo check ${CARGOFLAGS} --features replay
popd
pushd wrench
cargo check ${CARGOFLAGS} --features env_logger
popd
pushd examples
cargo check ${CARGOFLAGS}
popd
cargo test ${CARGOFLAGS} ${CARGOTESTFLAGS} \
--all --exclude compositor --exclude compositor-wayland \
--exclude compositor-windows --exclude glsl-to-cxx --exclude swgl

View file

@ -1,35 +0,0 @@
#!/usr/bin/env bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
# This must be run from the root webrender directory!
# Users may set the CARGOFLAGS environment variable to pass
# additional flags to cargo if desired.
# The WRENCH_BINARY environment variable, if set, is used to run
# the precached reftest.
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace
CARGOFLAGS=${CARGOFLAGS:-""} # default to empty if not set
WRENCH_BINARY=${WRENCH_BINARY:-""}
pushd wrench
# Test that all shaders compile successfully.
python script/headless.py --precache test_init
python script/headless.py --precache --use-unoptimized-shaders test_init
python script/headless.py reftest
python script/headless.py test_invalidation
if [[ -z "${WRENCH_BINARY}" ]]; then
cargo build ${CARGOFLAGS} --release
WRENCH_BINARY="../target/release/wrench"
fi
"${WRENCH_BINARY}" --precache \
reftest reftests/clip/fixed-position-clipping.yaml
popd

View file

@ -1,124 +0,0 @@
# http://blogs.technet.com/b/heyscriptingguy/archive/2010/07/07/hey-scripting-guy-how-can-i-change-my-desktop-monitor-resolution-via-windows-powershell.aspx
Function Set-ScreenResolution {
param (
[Parameter(Mandatory=$true,
Position = 0)]
[int]
$Width,
[Parameter(Mandatory=$true,
Position = 1)]
[int]
$Height
)
$pinvokeCode = @"
using System;
using System.Runtime.InteropServices;
namespace Resolution
{
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
};
class User_32
{
[DllImport("user32.dll")]
public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode);
[DllImport("user32.dll")]
public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags);
public const int ENUM_CURRENT_SETTINGS = -1;
public const int CDS_UPDATEREGISTRY = 0x01;
public const int CDS_TEST = 0x02;
public const int DISP_CHANGE_SUCCESSFUL = 0;
public const int DISP_CHANGE_RESTART = 1;
public const int DISP_CHANGE_FAILED = -1;
}
public class PrmaryScreenResolution
{
static public string ChangeResolution(int width, int height)
{
DEVMODE1 dm = GetDevMode1();
if (0 != User_32.EnumDisplaySettings(null, User_32.ENUM_CURRENT_SETTINGS, ref dm))
{
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
int iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_TEST);
if (iRet == User_32.DISP_CHANGE_FAILED)
{
return "Unable To Process Your Request. Sorry For This Inconvenience.";
}
else
{
iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_UPDATEREGISTRY);
switch (iRet)
{
case User_32.DISP_CHANGE_SUCCESSFUL:
{
return "Success";
}
case User_32.DISP_CHANGE_RESTART:
{
return "You Need To Reboot For The Change To Happen.\n If You Feel Any Problem After Rebooting Your Machine\nThen Try To Change Resolution In Safe Mode.";
}
default:
{
return "Failed To Change The Resolution";
}
}
}
}
else
{
return "Failed To Change The Resolution.";
}
}
private static DEVMODE1 GetDevMode1()
{
DEVMODE1 dm = new DEVMODE1();
dm.dmDeviceName = new String(new char[32]);
dm.dmFormName = new String(new char[32]);
dm.dmSize = (short)Marshal.SizeOf(dm);
return dm;
}
}
}
"@
Add-Type $pinvokeCode -ErrorAction SilentlyContinue
[Resolution.PrmaryScreenResolution]::ChangeResolution($width,$height)
}

View file

@ -1,38 +0,0 @@
:: This Source Code Form is subject to the terms of the Mozilla Public
:: License, v. 2.0. If a copy of the MPL was not distributed with this
:: file, You can obtain one at http://mozilla.org/MPL/2.0/. */
:: This must be run from the root webrender directory!
:: Users may set the CARGOFLAGS environment variable to pass
:: additional flags to cargo if desired.
if NOT DEFINED CARGOFLAGS SET CARGOFLAGS=--verbose
pushd webrender_api
cargo test %CARGOFLAGS%
if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
popd
pushd webrender
cargo test %CARGOFLAGS%
if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
popd
pushd wrench
cargo test --verbose
if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
:: Test that all shaders compile successfully. --precache compiles all shaders
:: during initialization, therefore if init is successful then the shaders compile.
cargo run --release -- --angle --precache test_init
if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
cargo run --release -- --angle --precache --use-unoptimized-shaders test_init
if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
cargo run --release -- --angle reftest
if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
popd
pushd examples
cargo check --verbose
if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
popd

View file

@ -1,11 +0,0 @@
[package]
name = "compositor-wayland"
version = "0.1.0"
authors = ["Glenn Watson <gw@intuitionlibrary.com>",
"Robert Mader <robert.mader@posteo.de>"]
edition = "2018"
license = "MPL-2.0"
[build-dependencies]
cc = "1.0"
pkg-config = "^0.3.17"

View file

@ -1,63 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::process::Command;
use std::env;
use std::fs;
extern crate pkg_config;
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
fs::create_dir_all(&format!("{}/include", out_dir)).unwrap();
Command::new("wayland-scanner")
.args(&["client-header", "/usr/share/wayland-protocols/stable/viewporter/viewporter.xml"])
.arg(&format!("{}/include/viewporter-client-protocol.h", out_dir))
.status().unwrap();
Command::new("wayland-scanner")
.args(&["public-code", "/usr/share/wayland-protocols/stable/viewporter/viewporter.xml"])
.arg(&format!("{}/viewporter-protocol.c", out_dir))
.status().unwrap();
Command::new("wayland-scanner")
.args(&["client-header", "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml"])
.arg(&format!("{}/include/xdg-shell-client-protocol.h", out_dir))
.status().unwrap();
Command::new("wayland-scanner")
.args(&["public-code", "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml"])
.arg(&format!("{}/xdg-shell-protocol.c", out_dir))
.status().unwrap();
cc::Build::new()
.include(&format!("{}/include", out_dir))
.file("src/lib.cpp")
.file(&format!("{}/viewporter-protocol.c", out_dir))
.file(&format!("{}/xdg-shell-protocol.c", out_dir))
.compile("wayland");
println!("cargo:rustc-link-lib=dylib=stdc++");
pkg_config::Config::new()
.atleast_version("1")
.probe("egl")
.unwrap();
pkg_config::Config::new()
.atleast_version("1")
.probe("gl")
.unwrap();
pkg_config::Config::new()
.atleast_version("1")
.probe("wayland-client")
.unwrap();
pkg_config::Config::new()
.atleast_version("1")
.probe("wayland-egl")
.unwrap();
println!("cargo:rerun-if-changed=src/lib.rs");
println!("cargo:rerun-if-changed=src/lib.cpp");
}

View file

@ -1,772 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#define UNICODE
#include <algorithm>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <map>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <unordered_map>
#include <vector>
#include <wayland-client.h>
#include <wayland-egl.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>
#include <GLES2/gl2.h>
#include "viewporter-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#define UNUSED(x) (void)(x)
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define NUM_QUERIES 2
#define VIRTUAL_OFFSET 512 * 1024
enum SyncMode {
None_ = 0,
Swap = 1,
Commit = 2,
Flush = 3,
Query = 4,
};
// The OS compositor representation of a picture cache tile.
struct Tile {
uint64_t surface_id;
int x;
int y;
struct wl_surface* surface;
struct wl_subsurface* subsurface;
struct wp_viewport* viewport;
struct wl_egl_window* egl_window;
EGLSurface egl_surface;
bool is_visible;
std::vector<EGLint> damage_rects;
};
struct TileKey {
int x;
int y;
TileKey(int ax, int ay) : x(ax), y(ay) {}
};
bool operator==(const TileKey& k0, const TileKey& k1) {
return k0.x == k1.x && k0.y == k1.y;
}
struct TileKeyHasher {
size_t operator()(const TileKey& key) const { return key.x ^ key.y; }
};
struct Surface {
uint64_t id;
int tile_width;
int tile_height;
bool is_opaque;
std::unordered_map<TileKey, Tile*, TileKeyHasher> tiles;
};
struct WLDisplay {
struct wl_display* display;
struct wl_registry* registry;
struct wl_compositor* compositor;
struct wl_subcompositor* subcompositor;
struct xdg_wm_base* wm_base;
struct wl_seat* seat;
struct wl_pointer* pointer;
struct wl_touch* touch;
struct wl_keyboard* keyboard;
struct wl_shm* shm;
struct wl_cursor_theme* cursor_theme;
struct wl_cursor* default_cursor;
struct wl_surface* cursor_surface;
struct wp_viewporter* viewporter;
PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage;
};
struct WLGeometry {
int width, height;
};
struct WLWindow {
WLGeometry geometry;
bool enable_compositor;
SyncMode sync_mode;
bool closed;
WLDisplay* display;
struct wl_surface* surface;
struct xdg_surface* xdg_surface;
struct xdg_toplevel* xdg_toplevel;
struct wl_callback* callback;
struct wp_viewport* viewport;
bool wait_for_configure;
struct wl_egl_window* egl_window;
EGLSurface egl_surface;
EGLDeviceEXT eglDevice;
EGLDisplay eglDisplay;
EGLContext eglContext;
EGLConfig config;
// Maintain list of layer state between frames to avoid visual tree rebuild.
std::vector<uint64_t> currentLayers;
std::vector<uint64_t> prevLayers;
// Maps WR surface IDs to each OS surface
std::unordered_map<uint64_t, Surface> surfaces;
std::vector<Tile*> destroyedTiles;
std::vector<Tile*> hiddenTiles;
};
extern "C" {
static void init_wl_registry(WLWindow* window);
static void init_xdg_window(WLWindow* window);
WLWindow* com_wl_create_window(int width, int height, bool enable_compositor,
SyncMode sync_mode) {
WLDisplay* display = new WLDisplay;
WLWindow* window = new WLWindow;
window->display = display;
window->geometry.width = width;
window->geometry.height = height;
window->enable_compositor = enable_compositor;
window->sync_mode = sync_mode;
window->closed = false;
display->display = wl_display_connect(NULL);
assert(display->display);
init_wl_registry(window);
if (enable_compositor && !display->viewporter) {
fprintf(stderr, "Native compositor mode requires wp_viewporter support\n");
window->closed = true;
}
window->eglDisplay =
eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, display->display, NULL);
eglInitialize(window->eglDisplay, nullptr, nullptr);
eglBindAPI(EGL_OPENGL_API);
EGLint num_configs = 0;
EGLint cfg_attribs[] = {EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_NONE};
EGLConfig configs[32];
eglChooseConfig(window->eglDisplay, cfg_attribs, configs,
sizeof(configs) / sizeof(EGLConfig), &num_configs);
assert(num_configs > 0);
window->config = configs[0];
EGLint ctx_attribs[] = {EGL_CONTEXT_OPENGL_PROFILE_MASK,
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
EGL_CONTEXT_MAJOR_VERSION,
3,
EGL_CONTEXT_MINOR_VERSION,
2,
EGL_NONE};
// Create an EGL context that can be used for drawing
window->eglContext = eglCreateContext(window->eglDisplay, window->config,
EGL_NO_CONTEXT, ctx_attribs);
window->surface = wl_compositor_create_surface(display->compositor);
init_xdg_window(window);
struct wl_region* region =
wl_compositor_create_region(window->display->compositor);
wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
wl_surface_set_opaque_region(window->surface, region);
wl_region_destroy(region);
if (enable_compositor) {
xdg_toplevel_set_title(window->xdg_toplevel,
"example-compositor (Wayland)");
} else {
xdg_toplevel_set_title(window->xdg_toplevel, "example-compositor (Simple)");
}
window->wait_for_configure = true;
wl_surface_commit(window->surface);
EGLBoolean ok = eglMakeCurrent(window->eglDisplay, EGL_NO_SURFACE,
EGL_NO_SURFACE, window->eglContext);
assert(ok);
display->swap_buffers_with_damage =
(PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC)eglGetProcAddress(
"eglSwapBuffersWithDamageKHR");
return window;
}
bool com_wl_tick(WLWindow* window) {
if (window->wait_for_configure) {
int ret = 0;
while (window->wait_for_configure && !window->closed && ret != -1) {
wl_display_dispatch(window->display->display);
}
} else {
wl_display_dispatch_pending(window->display->display);
}
return !window->closed;
}
static void unmap_hidden_tiles(WLWindow* window) {
for (Tile* tile : window->hiddenTiles) {
if (tile->subsurface) {
wl_subsurface_destroy(tile->subsurface);
tile->subsurface = nullptr;
}
}
window->hiddenTiles.clear();
}
static void clean_up_tiles(WLWindow* window) {
for (Tile* tile : window->destroyedTiles) {
eglDestroySurface(window->eglDisplay, tile->egl_surface);
wl_egl_window_destroy(tile->egl_window);
wp_viewport_destroy(tile->viewport);
wl_surface_destroy(tile->surface);
delete tile;
}
window->destroyedTiles.clear();
}
static void handle_callback(void* data, struct wl_callback* callback,
uint32_t time) {
WLWindow* window = (WLWindow*)data;
UNUSED(time);
assert(window->callback == callback);
wl_callback_destroy(callback);
window->callback = nullptr;
}
static const struct wl_callback_listener frame_listener = {handle_callback};
void com_wl_swap_buffers(WLWindow* window) {
if (window->enable_compositor) {
for (auto surface_it = window->surfaces.begin();
surface_it != window->surfaces.end(); ++surface_it) {
Surface* surface = &surface_it->second;
for (auto tile_it = surface->tiles.begin();
tile_it != surface->tiles.end(); ++tile_it) {
Tile* tile = tile_it->second;
if (!tile->damage_rects.empty() && tile->is_visible) {
eglMakeCurrent(window->eglDisplay, tile->egl_surface,
tile->egl_surface, window->eglContext);
eglSwapInterval(window->eglDisplay, 0);
/* if (window->display->swap_buffers_with_damage) {
window->display->swap_buffers_with_damage(
window->eglDisplay, tile->egl_surface,
tile->damage_rects.data(), tile->damage_rects.size() / 4);
} else */
eglSwapBuffers(window->eglDisplay, tile->egl_surface);
tile->damage_rects.clear();
eglMakeCurrent(window->eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
window->eglContext);
} else {
wl_surface_commit(tile->surface);
}
}
}
wl_surface_commit(window->surface);
unmap_hidden_tiles(window);
clean_up_tiles(window);
int ret = 0;
switch (window->sync_mode) {
case SyncMode::None_:
wl_display_roundtrip(window->display->display);
break;
case SyncMode::Swap:
window->callback = wl_surface_frame(window->surface);
wl_callback_add_listener(window->callback, &frame_listener, window);
wl_surface_commit(window->surface);
while (window->callback && !window->closed && ret != -1) {
ret = wl_display_dispatch(window->display->display);
}
break;
default:
assert(false);
break;
}
} else {
// If not using native mode, then do a normal EGL swap buffers.
switch (window->sync_mode) {
case SyncMode::None_:
eglSwapInterval(window->eglDisplay, 0);
break;
case SyncMode::Swap:
eglSwapInterval(window->eglDisplay, 1);
break;
default:
assert(false);
break;
}
eglSwapBuffers(window->eglDisplay, window->egl_surface);
}
}
// Create a new native surface
void com_wl_create_surface(WLWindow* window, uint64_t surface_id,
int tile_width, int tile_height, bool is_opaque) {
assert(window->surfaces.count(surface_id) == 0);
Surface surface;
surface.id = surface_id;
surface.tile_width = tile_width;
surface.tile_height = tile_height;
surface.is_opaque = is_opaque;
window->surfaces.emplace(surface_id, surface);
}
void com_wl_create_tile(WLWindow* window, uint64_t surface_id, int x, int y) {
WLDisplay* display = window->display;
assert(window->surfaces.count(surface_id) == 1);
Surface* surface = &window->surfaces.at(surface_id);
TileKey key(x, y);
assert(surface->tiles.count(key) == 0);
Tile* tile = new Tile;
tile->surface_id = surface_id;
tile->x = x;
tile->y = y;
tile->is_visible = false;
tile->surface = wl_compositor_create_surface(display->compositor);
tile->viewport =
wp_viewporter_get_viewport(display->viewporter, tile->surface);
if (surface->is_opaque) {
struct wl_region* region =
wl_compositor_create_region(window->display->compositor);
wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
wl_surface_set_opaque_region(tile->surface, region);
wl_region_destroy(region);
}
tile->egl_window = wl_egl_window_create(tile->surface, surface->tile_width,
surface->tile_height);
tile->egl_surface = eglCreateWindowSurface(window->eglDisplay, window->config,
tile->egl_window, NULL);
assert(tile->egl_surface != EGL_NO_SURFACE);
surface->tiles.emplace(key, tile);
}
static void show_tile(WLWindow* window, Tile* tile) {
if (tile->is_visible) {
assert(tile->subsurface);
return;
}
tile->subsurface = wl_subcompositor_get_subsurface(
window->display->subcompositor, tile->surface, window->surface);
/* This is not comprehensive yet, see hide_tile() */
Surface* surface = &window->surfaces.at(tile->surface_id);
for (auto tile_it = surface->tiles.begin(); tile_it != surface->tiles.end();
++tile_it) {
Tile* other_tile = tile_it->second;
if (other_tile->is_visible) {
wl_subsurface_place_above(tile->subsurface, other_tile->surface);
}
}
tile->is_visible = true;
}
static void hide_tile(WLWindow* window, Tile* tile) {
if (!tile->is_visible) {
return;
}
/*
* This is a workaround for missing API on the egl-wayland platform. We
* likely want to replace it a solution that detaches the buffer from
* the surface, which would require us to manage buffers manually.
*/
wl_subsurface_set_position(tile->subsurface, window->geometry.width / 2,
window->geometry.height / 2);
wp_viewport_set_source(tile->viewport, wl_fixed_from_int(0),
wl_fixed_from_int(0), wl_fixed_from_int(1),
wl_fixed_from_int(1));
wl_subsurface_place_below(tile->subsurface, window->surface);
tile->is_visible = false;
window->hiddenTiles.push_back(tile);
}
void com_wl_destroy_tile(WLWindow* window, uint64_t surface_id, int x, int y) {
assert(window->surfaces.count(surface_id) == 1);
Surface* surface = &window->surfaces.at(surface_id);
TileKey key(x, y);
assert(surface->tiles.count(key) == 1);
Tile* tile = surface->tiles[key];
hide_tile(window, tile);
wl_surface_commit(tile->surface);
window->destroyedTiles.push_back(tile);
surface->tiles.erase(key);
}
void com_wl_destroy_surface(WLWindow* window, uint64_t surface_id) {
assert(window->surfaces.count(surface_id) == 1);
Surface* surface = &window->surfaces.at(surface_id);
for (auto tile_it = surface->tiles.begin(); tile_it != surface->tiles.end();
tile_it = surface->tiles.begin()) {
Tile* tile = tile_it->second;
com_wl_destroy_tile(window, surface_id, tile->x, tile->y);
}
window->surfaces.erase(surface_id);
}
void com_wl_destroy_window(WLWindow* window) {
for (auto surface_it = window->surfaces.begin();
surface_it != window->surfaces.end(); ++surface_it) {
Surface& surface = surface_it->second;
com_wl_destroy_surface(window, surface.id);
}
if (window->egl_surface != EGL_NO_SURFACE) {
eglDestroySurface(window->eglDisplay, window->egl_surface);
}
eglDestroyContext(window->eglDisplay, window->eglContext);
eglTerminate(window->eglDisplay);
delete window;
}
// Bind a native surface to allow issuing GL commands to it
GLuint com_wl_bind_surface(WLWindow* window, uint64_t surface_id, int tile_x,
int tile_y, int* x_offset, int* y_offset,
int dirty_x0, int dirty_y0, int dirty_width,
int dirty_height) {
*x_offset = 0;
*y_offset = 0;
assert(window->surfaces.count(surface_id) == 1);
Surface* surface = &window->surfaces[surface_id];
TileKey key(tile_x, tile_y);
assert(surface->tiles.count(key) == 1);
Tile* tile = surface->tiles[key];
tile->damage_rects.push_back(dirty_x0);
tile->damage_rects.push_back(dirty_y0);
tile->damage_rects.push_back(dirty_width);
tile->damage_rects.push_back(dirty_height);
EGLBoolean ok = eglMakeCurrent(window->eglDisplay, tile->egl_surface,
tile->egl_surface, window->eglContext);
assert(ok);
return 0;
}
// Unbind a currently bound native surface
void com_wl_unbind_surface(WLWindow* window) {
eglMakeCurrent(window->eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
window->eglContext);
}
void com_wl_begin_transaction(WLWindow*) {}
// Add a native surface to the visual tree. Called per-frame to build the
// composition.
void com_wl_add_surface(WLWindow* window, uint64_t surface_id, int offset_x,
int offset_y, int clip_x, int clip_y, int clip_w,
int clip_h) {
Surface* surface = &window->surfaces[surface_id];
window->currentLayers.push_back(surface_id);
for (auto tile_it = surface->tiles.begin(); tile_it != surface->tiles.end();
++tile_it) {
Tile* tile = tile_it->second;
int pos_x = MAX((tile->x * surface->tile_width) + offset_x, clip_x);
int pos_y = MAX((tile->y * surface->tile_height) + offset_y, clip_y);
float view_x = MAX((clip_x - offset_x) - tile->x * surface->tile_width, 0);
float view_y = MAX((clip_y - offset_y) - tile->y * surface->tile_height, 0);
float view_w = MIN(surface->tile_width - view_x, (clip_x + clip_w) - pos_x);
float view_h =
MIN(surface->tile_height - view_y, (clip_y + clip_h) - pos_y);
view_w = MIN(window->geometry.width - pos_x, view_w);
view_h = MIN(window->geometry.height - pos_y, view_h);
if (view_w > 0 && view_h > 0) {
show_tile(window, tile);
wl_surface_set_buffer_transform(tile->surface,
WL_OUTPUT_TRANSFORM_FLIPPED_180);
wl_subsurface_set_position(tile->subsurface, pos_x, pos_y);
wp_viewport_set_source(tile->viewport, wl_fixed_from_double(view_x),
wl_fixed_from_double(view_y),
wl_fixed_from_double(view_w),
wl_fixed_from_double(view_h));
} else {
hide_tile(window, tile);
}
}
}
void com_wl_end_transaction(WLWindow* window) {
bool same = window->prevLayers == window->currentLayers;
if (!same) {
struct wl_surface* prev_surface = window->surface;
for (auto it = window->currentLayers.begin();
it != window->currentLayers.end(); ++it) {
Surface* surface = &window->surfaces[*it];
struct wl_surface* next_surface = nullptr;
for (auto tile_it = surface->tiles.begin();
tile_it != surface->tiles.end(); ++tile_it) {
Tile* tile = tile_it->second;
if (tile->is_visible) {
wl_subsurface_place_above(tile->subsurface, prev_surface);
if (!next_surface) {
next_surface = tile->surface;
}
}
}
prev_surface = next_surface;
}
}
window->prevLayers.swap(window->currentLayers);
window->currentLayers.clear();
}
void glInvalidateFramebuffer(GLenum target, GLsizei numAttachments,
const GLenum* attachments) {
UNUSED(target);
UNUSED(numAttachments);
UNUSED(attachments);
}
// Get a pointer to an EGL symbol
void* com_wl_get_proc_address(const char* name) {
/* Disable glInvalidateFramebuffer for now as it triggers errors.
* This is likely due to the egl-wayland platform, which we may want to
* replace with a custom implementation in order to have more control
* over the low-lever bits.
*/
if (strcmp(name, "glInvalidateFramebuffer") == 0) {
return (void*)glInvalidateFramebuffer;
}
return (void*)eglGetProcAddress(name);
}
void com_wl_deinit(WLWindow* window) { UNUSED(window); }
static void handle_xdg_surface_configure(void* data,
struct xdg_surface* surface,
uint32_t serial) {
WLWindow* window = (WLWindow*)data;
xdg_surface_ack_configure(surface, serial);
if (window->wait_for_configure) {
if (window->enable_compositor) {
int width = window->geometry.width;
int height = window->geometry.height;
window->egl_window = wl_egl_window_create(window->surface, 1, 1);
window->egl_surface = eglCreateWindowSurface(
window->eglDisplay, window->config, window->egl_window, NULL);
assert(window->egl_surface != EGL_NO_SURFACE);
EGLBoolean ok = eglMakeCurrent(window->eglDisplay, window->egl_surface,
window->egl_surface, window->eglContext);
assert(ok);
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
window->viewport = wp_viewporter_get_viewport(window->display->viewporter,
window->surface);
wp_viewport_set_destination(window->viewport, width, height);
eglSwapBuffers(window->eglDisplay, window->egl_surface);
} else {
window->egl_window = wl_egl_window_create(
window->surface, window->geometry.width, window->geometry.height);
window->egl_surface = eglCreateWindowSurface(
window->eglDisplay, window->config, window->egl_window, NULL);
assert(window->egl_surface != EGL_NO_SURFACE);
EGLBoolean ok = eglMakeCurrent(window->eglDisplay, window->egl_surface,
window->egl_surface, window->eglContext);
assert(ok);
}
}
window->wait_for_configure = false;
}
static const struct xdg_surface_listener xdg_surface_listener = {
handle_xdg_surface_configure};
static void handle_xdg_toplevel_configure(void* data,
struct xdg_toplevel* toplevel,
int32_t width, int32_t height,
struct wl_array* states) {
WLWindow* window = (WLWindow*)data;
UNUSED(toplevel);
UNUSED(states);
if (width > 0 && height > 0) {
window->geometry.width = width;
window->geometry.height = height;
if (!window->wait_for_configure) {
if (window->enable_compositor) {
wp_viewport_set_destination(window->viewport, window->geometry.width,
window->geometry.height);
} else {
wl_egl_window_resize(window->egl_window, window->geometry.width,
window->geometry.height, 0, 0);
}
}
}
}
static void handle_xdg_toplevel_close(void* data,
struct xdg_toplevel* toplevel) {
UNUSED(toplevel);
WLWindow* window = (WLWindow*)data;
window->closed = true;
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
handle_xdg_toplevel_configure,
handle_xdg_toplevel_close,
};
static void xdg_wm_base_ping(void* data, struct xdg_wm_base* shell,
uint32_t serial) {
UNUSED(data);
xdg_wm_base_pong(shell, serial);
}
static const struct xdg_wm_base_listener wm_base_listener = {
xdg_wm_base_ping,
};
static void registry_handle_global(void* data, struct wl_registry* registry,
uint32_t name, const char* interface,
uint32_t version) {
WLDisplay* d = (WLDisplay*)data;
if (strcmp(interface, "wl_compositor") == 0) {
d->compositor = (struct wl_compositor*)wl_registry_bind(
registry, name, &wl_compositor_interface, MIN(version, 4));
} else if (strcmp(interface, "wp_viewporter") == 0) {
d->viewporter = (struct wp_viewporter*)wl_registry_bind(
registry, name, &wp_viewporter_interface, 1);
} else if (strcmp(interface, "xdg_wm_base") == 0) {
d->wm_base = (struct xdg_wm_base*)wl_registry_bind(
registry, name, &xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, NULL);
} else if (strcmp(interface, "wl_subcompositor") == 0) {
d->subcompositor = (struct wl_subcompositor*)wl_registry_bind(
registry, name, &wl_subcompositor_interface, 1);
}
}
static void registry_handle_global_remove(void* data,
struct wl_registry* registry,
uint32_t name) {
UNUSED(data);
UNUSED(registry);
UNUSED(name);
}
static const struct wl_registry_listener registry_listener = {
registry_handle_global, registry_handle_global_remove};
static void init_wl_registry(WLWindow* window) {
WLDisplay* display = window->display;
display->registry = wl_display_get_registry(display->display);
wl_registry_add_listener(display->registry, &registry_listener, display);
wl_display_roundtrip(display->display);
assert(display->compositor);
assert(display->wm_base);
assert(display->subcompositor);
}
static void init_xdg_window(WLWindow* window) {
window->xdg_surface =
xdg_wm_base_get_xdg_surface(window->display->wm_base, window->surface);
assert(window->xdg_surface);
xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window);
window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener,
window);
assert(window->xdg_toplevel);
}
}

View file

@ -1,269 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::os::raw::{c_void, c_char};
/*
This is a very simple (and unsafe!) rust wrapper for the Wayland / EGL
implementation in lib.cpp.
It just proxies the calls from the Compositor impl to the C99 code. This is very
hacky and not suitable for production!
*/
// Opaque wrapper for the Window type in lib.cpp
#[repr(C)]
pub struct Window {
_unused: [u8; 0]
}
// C99 functions that do the compositor work
extern {
fn com_wl_create_window(
width: i32,
height: i32,
enable_compositor: bool,
sync_mode: i32,
) -> *mut Window;
fn com_wl_destroy_window(window: *mut Window);
fn com_wl_tick(window: *mut Window) -> bool;
fn com_wl_get_proc_address(name: *const c_char) -> *const c_void;
fn com_wl_swap_buffers(window: *mut Window);
fn com_wl_create_surface(
window: *mut Window,
id: u64,
tile_width: i32,
tile_height: i32,
is_opaque: bool,
);
fn com_wl_create_tile(
window: *mut Window,
id: u64,
x: i32,
y: i32,
);
fn com_wl_destroy_tile(
window: *mut Window,
id: u64,
x: i32,
y: i32,
);
fn com_wl_destroy_surface(
window: *mut Window,
id: u64,
);
fn com_wl_bind_surface(
window: *mut Window,
surface_id: u64,
tile_x: i32,
tile_y: i32,
x_offset: &mut i32,
y_offset: &mut i32,
dirty_x0: i32,
dirty_y0: i32,
dirty_width: i32,
dirty_height: i32,
) -> u32;
fn com_wl_unbind_surface(window: *mut Window);
fn com_wl_begin_transaction(window: *mut Window);
fn com_wl_add_surface(
window: *mut Window,
id: u64,
x: i32,
y: i32,
clip_x: i32,
clip_y: i32,
clip_w: i32,
clip_h: i32,
);
fn com_wl_end_transaction(window: *mut Window);
fn com_wl_deinit(window: *mut Window);
}
pub fn create_window(
width: i32,
height: i32,
enable_compositor: bool,
sync_mode: i32,
) -> *mut Window {
unsafe {
com_wl_create_window(width, height, enable_compositor, sync_mode)
}
}
pub fn destroy_window(window: *mut Window) {
unsafe {
com_wl_destroy_window(window);
}
}
pub fn tick(window: *mut Window) -> bool {
unsafe {
com_wl_tick(window)
}
}
pub fn get_proc_address(name: *const c_char) -> *const c_void {
unsafe {
com_wl_get_proc_address(name)
}
}
pub fn create_surface(
window: *mut Window,
id: u64,
tile_width: i32,
tile_height: i32,
is_opaque: bool,
) {
unsafe {
com_wl_create_surface(
window,
id,
tile_width,
tile_height,
is_opaque,
)
}
}
pub fn create_tile(
window: *mut Window,
id: u64,
x: i32,
y: i32,
) {
unsafe {
com_wl_create_tile(
window,
id,
x,
y,
)
}
}
pub fn destroy_tile(
window: *mut Window,
id: u64,
x: i32,
y: i32,
) {
unsafe {
com_wl_destroy_tile(
window,
id,
x,
y,
)
}
}
pub fn destroy_surface(
window: *mut Window,
id: u64,
) {
unsafe {
com_wl_destroy_surface(
window,
id,
)
}
}
pub fn bind_surface(
window: *mut Window,
surface_id: u64,
tile_x: i32,
tile_y: i32,
dirty_x0: i32,
dirty_y0: i32,
dirty_width: i32,
dirty_height: i32,
) -> (u32, i32, i32) {
unsafe {
let mut x_offset = 0;
let mut y_offset = 0;
let fbo_id = com_wl_bind_surface(
window,
surface_id,
tile_x,
tile_y,
&mut x_offset,
&mut y_offset,
dirty_x0,
dirty_y0,
dirty_width,
dirty_height,
);
(fbo_id, x_offset, y_offset)
}
}
pub fn add_surface(
window: *mut Window,
id: u64,
x: i32,
y: i32,
clip_x: i32,
clip_y: i32,
clip_w: i32,
clip_h: i32,
) {
unsafe {
com_wl_add_surface(
window,
id,
x,
y,
clip_x,
clip_y,
clip_w,
clip_h,
)
}
}
pub fn begin_transaction(window: *mut Window) {
unsafe {
com_wl_begin_transaction(window)
}
}
pub fn unbind_surface(window: *mut Window) {
unsafe {
com_wl_unbind_surface(window)
}
}
pub fn end_transaction(window: *mut Window) {
unsafe {
com_wl_end_transaction(window)
}
}
pub fn swap_buffers(window: *mut Window) {
unsafe {
com_wl_swap_buffers(window);
}
}
pub fn deinit(window: *mut Window) {
unsafe {
com_wl_deinit(window);
}
}

View file

@ -1,9 +0,0 @@
[package]
name = "compositor-windows"
version = "0.1.0"
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
edition = "2018"
license = "MPL-2.0"
[build-dependencies]
cc = "1.0"

View file

@ -1,25 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
fn main() {
// HACK - This build script relies on Gecko having been built, so that the ANGLE libraries
// have already been compiled. It also assumes they are being built with an in-tree
// x86_64 object directory.
cc::Build::new()
.file("src/lib.cpp")
.include("../../../angle/checkout/include")
.compile("windows");
// Set up linker paths for ANGLE that is built by Gecko
println!("cargo:rustc-link-search=../../obj-x86_64-pc-mingw32/gfx/angle/targets/libEGL");
println!("cargo:rustc-link-search=../../obj-x86_64-pc-mingw32/gfx/angle/targets/libGLESv2");
// Link to libEGL and libGLESv2 (ANGLE) and D3D11 + DirectComposition
println!("cargo:rustc-link-lib=libEGL");
println!("cargo:rustc-link-lib=libGLESv2");
println!("cargo:rustc-link-lib=dcomp");
println!("cargo:rustc-link-lib=d3d11");
println!("cargo:rustc-link-lib=dwmapi");
}

View file

@ -1,694 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#define UNICODE
#include <windows.h>
#include <math.h>
#include <dcomp.h>
#include <d3d11.h>
#include <assert.h>
#include <map>
#include <vector>
#include <dwmapi.h>
#include <unordered_map>
#define EGL_EGL_PROTOTYPES 1
#define EGL_EGLEXT_PROTOTYPES 1
#define GL_GLEXT_PROTOTYPES 1
#include "EGL/egl.h"
#include "EGL/eglext.h"
#include "EGL/eglext_angle.h"
#include "GL/gl.h"
#include "GLES/gl.h"
#include "GLES/glext.h"
#include "GLES3/gl3.h"
#define NUM_QUERIES 2
#define USE_VIRTUAL_SURFACES
#define VIRTUAL_OFFSET 512 * 1024
enum SyncMode {
None = 0,
Swap = 1,
Commit = 2,
Flush = 3,
Query = 4,
};
// The OS compositor representation of a picture cache tile.
struct Tile {
#ifndef USE_VIRTUAL_SURFACES
// Represents the underlying DirectComposition surface texture that gets drawn
// into.
IDCompositionSurface* pSurface;
// Represents the node in the visual tree that defines the properties of this
// tile (clip, position etc).
IDCompositionVisual2* pVisual;
#endif
};
struct TileKey {
int x;
int y;
TileKey(int ax, int ay) : x(ax), y(ay) {}
};
bool operator==(const TileKey& k0, const TileKey& k1) {
return k0.x == k1.x && k0.y == k1.y;
}
struct TileKeyHasher {
size_t operator()(const TileKey& key) const { return key.x ^ key.y; }
};
struct Surface {
int tile_width;
int tile_height;
bool is_opaque;
std::unordered_map<TileKey, Tile, TileKeyHasher> tiles;
IDCompositionVisual2* pVisual;
#ifdef USE_VIRTUAL_SURFACES
IDCompositionVirtualSurface* pVirtualSurface;
#endif
};
struct CachedFrameBuffer {
int width;
int height;
GLuint fboId;
GLuint depthRboId;
};
struct Window {
// Win32 window details
HWND hWnd;
HINSTANCE hInstance;
bool enable_compositor;
RECT client_rect;
SyncMode sync_mode;
// Main interfaces to D3D11 and DirectComposition
ID3D11Device* pD3D11Device;
IDCompositionDesktopDevice* pDCompDevice;
IDCompositionTarget* pDCompTarget;
IDXGIDevice* pDXGIDevice;
ID3D11Query* pQueries[NUM_QUERIES];
int current_query;
// ANGLE interfaces that wrap the D3D device
EGLDeviceEXT EGLDevice;
EGLDisplay EGLDisplay;
EGLContext EGLContext;
EGLConfig config;
// Framebuffer surface for debug mode when we are not using DC
EGLSurface fb_surface;
// The currently bound surface, valid during bind() and unbind()
IDCompositionSurface* pCurrentSurface;
EGLImage mEGLImage;
GLuint mColorRBO;
// The root of the DC visual tree. Nothing is drawn on this, but
// all child tiles are parented to here.
IDCompositionVisual2* pRoot;
IDCompositionVisualDebug* pVisualDebug;
std::vector<CachedFrameBuffer> mFrameBuffers;
// Maintain list of layer state between frames to avoid visual tree rebuild.
std::vector<uint64_t> mCurrentLayers;
std::vector<uint64_t> mPrevLayers;
// Maps WR surface IDs to each OS surface
std::unordered_map<uint64_t, Surface> surfaces;
};
static const wchar_t* CLASS_NAME = L"WR DirectComposite";
static GLuint GetOrCreateFbo(Window* window, int aWidth, int aHeight) {
GLuint fboId = 0;
// Check if we have a cached FBO with matching dimensions
for (auto it = window->mFrameBuffers.begin();
it != window->mFrameBuffers.end(); ++it) {
if (it->width == aWidth && it->height == aHeight) {
fboId = it->fboId;
break;
}
}
// If not, create a new FBO with attached depth buffer
if (fboId == 0) {
// Create the depth buffer
GLuint depthRboId;
glGenRenderbuffers(1, &depthRboId);
glBindRenderbuffer(GL_RENDERBUFFER, depthRboId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, aWidth,
aHeight);
// Create the framebuffer and attach the depth buffer to it
glGenFramebuffers(1, &fboId);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, depthRboId);
// Store this in the cache for future calls.
CachedFrameBuffer frame_buffer_info;
frame_buffer_info.width = aWidth;
frame_buffer_info.height = aHeight;
frame_buffer_info.fboId = fboId;
frame_buffer_info.depthRboId = depthRboId;
window->mFrameBuffers.push_back(frame_buffer_info);
}
return fboId;
}
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam) {
switch (message) {
case WM_DESTROY:
PostQuitMessage(0);
return 1;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
extern "C" {
Window* com_dc_create_window(int width, int height, bool enable_compositor,
SyncMode sync_mode) {
// Create a simple Win32 window
Window* window = new Window;
window->hInstance = GetModuleHandle(NULL);
window->enable_compositor = enable_compositor;
window->mEGLImage = EGL_NO_IMAGE;
window->sync_mode = sync_mode;
WNDCLASSEX wcex = {sizeof(WNDCLASSEX)};
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = window->hInstance;
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
;
wcex.lpszMenuName = nullptr;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszClassName = CLASS_NAME;
RegisterClassEx(&wcex);
int dpiX = 0;
int dpiY = 0;
HDC hdc = GetDC(NULL);
if (hdc) {
dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
}
RECT window_rect = {0, 0, width, height};
AdjustWindowRect(&window_rect, WS_OVERLAPPEDWINDOW, FALSE);
UINT window_width = static_cast<UINT>(
ceil(float(window_rect.right - window_rect.left) * dpiX / 96.f));
UINT window_height = static_cast<UINT>(
ceil(float(window_rect.bottom - window_rect.top) * dpiY / 96.f));
LPCWSTR name;
DWORD style;
if (enable_compositor) {
name = L"example-compositor (DirectComposition)";
style = WS_EX_NOREDIRECTIONBITMAP;
} else {
name = L"example-compositor (Simple)";
style = 0;
}
window->hWnd =
CreateWindowEx(style, CLASS_NAME, name, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, window_width, window_height,
NULL, NULL, window->hInstance, NULL);
ShowWindow(window->hWnd, SW_SHOWNORMAL);
UpdateWindow(window->hWnd);
GetClientRect(window->hWnd, &window->client_rect);
// Create a D3D11 device
D3D_FEATURE_LEVEL featureLevelSupported;
HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL,
D3D11_CREATE_DEVICE_BGRA_SUPPORT, NULL, 0,
D3D11_SDK_VERSION, &window->pD3D11Device,
&featureLevelSupported, nullptr);
assert(SUCCEEDED(hr));
D3D11_QUERY_DESC query_desc;
memset(&query_desc, 0, sizeof(query_desc));
query_desc.Query = D3D11_QUERY_EVENT;
for (int i = 0; i < NUM_QUERIES; ++i) {
hr = window->pD3D11Device->CreateQuery(&query_desc, &window->pQueries[i]);
assert(SUCCEEDED(hr));
}
window->current_query = 0;
hr = window->pD3D11Device->QueryInterface(&window->pDXGIDevice);
assert(SUCCEEDED(hr));
// Create a DirectComposition device
hr = DCompositionCreateDevice2(window->pDXGIDevice,
__uuidof(IDCompositionDesktopDevice),
(void**)&window->pDCompDevice);
assert(SUCCEEDED(hr));
// Create a DirectComposition target for a Win32 window handle
hr = window->pDCompDevice->CreateTargetForHwnd(window->hWnd, TRUE,
&window->pDCompTarget);
assert(SUCCEEDED(hr));
// Create an ANGLE EGL device that wraps D3D11
window->EGLDevice = eglCreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE,
window->pD3D11Device, nullptr);
EGLint display_attribs[] = {EGL_NONE};
window->EGLDisplay = eglGetPlatformDisplayEXT(
EGL_PLATFORM_DEVICE_EXT, window->EGLDevice, display_attribs);
eglInitialize(window->EGLDisplay, nullptr, nullptr);
EGLint num_configs = 0;
EGLint cfg_attribs[] = {EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_NONE};
EGLConfig configs[32];
eglChooseConfig(window->EGLDisplay, cfg_attribs, configs,
sizeof(configs) / sizeof(EGLConfig), &num_configs);
assert(num_configs > 0);
window->config = configs[0];
if (window->enable_compositor) {
window->fb_surface = EGL_NO_SURFACE;
} else {
window->fb_surface = eglCreateWindowSurface(
window->EGLDisplay, window->config, window->hWnd, NULL);
assert(window->fb_surface != EGL_NO_SURFACE);
}
EGLint ctx_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
// Create an EGL context that can be used for drawing
window->EGLContext = eglCreateContext(window->EGLDisplay, window->config,
EGL_NO_CONTEXT, ctx_attribs);
// Create the root of the DirectComposition visual tree
hr = window->pDCompDevice->CreateVisual(&window->pRoot);
assert(SUCCEEDED(hr));
hr = window->pDCompTarget->SetRoot(window->pRoot);
assert(SUCCEEDED(hr));
hr = window->pRoot->QueryInterface(__uuidof(IDCompositionVisualDebug),
(void**)&window->pVisualDebug);
assert(SUCCEEDED(hr));
// Uncomment this to see redraw regions during composite
// window->pVisualDebug->EnableRedrawRegions();
EGLBoolean ok = eglMakeCurrent(window->EGLDisplay, window->fb_surface,
window->fb_surface, window->EGLContext);
assert(ok);
return window;
}
void com_dc_destroy_window(Window* window) {
for (auto surface_it = window->surfaces.begin();
surface_it != window->surfaces.end(); ++surface_it) {
Surface& surface = surface_it->second;
#ifndef USE_VIRTUAL_SURFACES
for (auto tile_it = surface.tiles.begin(); tile_it != surface.tiles.end();
++tile_it) {
tile_it->second.pSurface->Release();
tile_it->second.pVisual->Release();
}
#endif
surface.pVisual->Release();
}
if (window->fb_surface != EGL_NO_SURFACE) {
eglDestroySurface(window->EGLDisplay, window->fb_surface);
}
eglDestroyContext(window->EGLDisplay, window->EGLContext);
eglTerminate(window->EGLDisplay);
eglReleaseDeviceANGLE(window->EGLDevice);
for (int i = 0; i < NUM_QUERIES; ++i) {
window->pQueries[i]->Release();
}
window->pRoot->Release();
window->pVisualDebug->Release();
window->pD3D11Device->Release();
window->pDXGIDevice->Release();
window->pDCompDevice->Release();
window->pDCompTarget->Release();
CloseWindow(window->hWnd);
UnregisterClass(CLASS_NAME, window->hInstance);
delete window;
}
bool com_dc_tick(Window*) {
// Check and dispatch the windows event loop
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
return false;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return true;
}
void com_dc_swap_buffers(Window* window) {
// If not using DC mode, then do a normal EGL swap buffers.
if (window->fb_surface != EGL_NO_SURFACE) {
switch (window->sync_mode) {
case SyncMode::None:
eglSwapInterval(window->EGLDisplay, 0);
break;
case SyncMode::Swap:
eglSwapInterval(window->EGLDisplay, 1);
break;
default:
assert(false); // unexpected vsync mode for simple compositor.
break;
}
eglSwapBuffers(window->EGLDisplay, window->fb_surface);
} else {
switch (window->sync_mode) {
case SyncMode::None:
break;
case SyncMode::Commit:
window->pDCompDevice->WaitForCommitCompletion();
break;
case SyncMode::Flush:
DwmFlush();
break;
case SyncMode::Query:
// todo!!!!
break;
default:
assert(false); // unexpected vsync mode for native compositor
break;
}
}
}
// Create a new DC surface
void com_dc_create_surface(Window* window, uint64_t id, int tile_width,
int tile_height, bool is_opaque) {
assert(window->surfaces.count(id) == 0);
Surface surface;
surface.tile_width = tile_width;
surface.tile_height = tile_height;
surface.is_opaque = is_opaque;
// Create the visual node in the DC tree that stores properties
HRESULT hr = window->pDCompDevice->CreateVisual(&surface.pVisual);
assert(SUCCEEDED(hr));
#ifdef USE_VIRTUAL_SURFACES
DXGI_ALPHA_MODE alpha_mode = surface.is_opaque
? DXGI_ALPHA_MODE_IGNORE
: DXGI_ALPHA_MODE_PREMULTIPLIED;
hr = window->pDCompDevice->CreateVirtualSurface(
VIRTUAL_OFFSET * 2, VIRTUAL_OFFSET * 2, DXGI_FORMAT_B8G8R8A8_UNORM,
alpha_mode, &surface.pVirtualSurface);
assert(SUCCEEDED(hr));
// Bind the surface memory to this visual
hr = surface.pVisual->SetContent(surface.pVirtualSurface);
assert(SUCCEEDED(hr));
#endif
window->surfaces[id] = surface;
}
void com_dc_create_tile(Window* window, uint64_t id, int x, int y) {
assert(window->surfaces.count(id) == 1);
Surface& surface = window->surfaces[id];
TileKey key(x, y);
assert(surface.tiles.count(key) == 0);
Tile tile;
#ifndef USE_VIRTUAL_SURFACES
// Create the video memory surface.
DXGI_ALPHA_MODE alpha_mode = surface.is_opaque
? DXGI_ALPHA_MODE_IGNORE
: DXGI_ALPHA_MODE_PREMULTIPLIED;
HRESULT hr = window->pDCompDevice->CreateSurface(
surface.tile_width, surface.tile_height, DXGI_FORMAT_B8G8R8A8_UNORM,
alpha_mode, &tile.pSurface);
assert(SUCCEEDED(hr));
// Create the visual node in the DC tree that stores properties
hr = window->pDCompDevice->CreateVisual(&tile.pVisual);
assert(SUCCEEDED(hr));
// Bind the surface memory to this visual
hr = tile.pVisual->SetContent(tile.pSurface);
assert(SUCCEEDED(hr));
// Place the visual in local-space of this surface
float offset_x = (float)(x * surface.tile_width);
float offset_y = (float)(y * surface.tile_height);
tile.pVisual->SetOffsetX(offset_x);
tile.pVisual->SetOffsetY(offset_y);
surface.pVisual->AddVisual(tile.pVisual, FALSE, NULL);
#endif
surface.tiles[key] = tile;
}
void com_dc_destroy_tile(Window* window, uint64_t id, int x, int y) {
assert(window->surfaces.count(id) == 1);
Surface& surface = window->surfaces[id];
TileKey key(x, y);
assert(surface.tiles.count(key) == 1);
Tile& tile = surface.tiles[key];
#ifndef USE_VIRTUAL_SURFACES
surface.pVisual->RemoveVisual(tile.pVisual);
tile.pVisual->Release();
tile.pSurface->Release();
#endif
surface.tiles.erase(key);
}
void com_dc_destroy_surface(Window* window, uint64_t id) {
assert(window->surfaces.count(id) == 1);
Surface& surface = window->surfaces[id];
window->pRoot->RemoveVisual(surface.pVisual);
#ifdef USE_VIRTUAL_SURFACES
surface.pVirtualSurface->Release();
#else
// Release the video memory and visual in the tree
for (auto tile_it = surface.tiles.begin(); tile_it != surface.tiles.end();
++tile_it) {
tile_it->second.pSurface->Release();
tile_it->second.pVisual->Release();
}
#endif
surface.pVisual->Release();
window->surfaces.erase(id);
}
// Bind a DC surface to allow issuing GL commands to it
GLuint com_dc_bind_surface(Window* window, uint64_t surface_id, int tile_x,
int tile_y, int* x_offset, int* y_offset,
int dirty_x0, int dirty_y0, int dirty_width,
int dirty_height) {
assert(window->surfaces.count(surface_id) == 1);
Surface& surface = window->surfaces[surface_id];
TileKey key(tile_x, tile_y);
assert(surface.tiles.count(key) == 1);
Tile& tile = surface.tiles[key];
// Inform DC that we want to draw on this surface. DC uses texture
// atlases when the tiles are small. It returns an offset where the
// client code must draw into this surface when this happens.
RECT update_rect;
update_rect.left = dirty_x0;
update_rect.top = dirty_y0;
update_rect.right = dirty_x0 + dirty_width;
update_rect.bottom = dirty_y0 + dirty_height;
POINT offset;
D3D11_TEXTURE2D_DESC desc;
ID3D11Texture2D* pTexture;
HRESULT hr;
// Store the current surface for unbinding later
#ifdef USE_VIRTUAL_SURFACES
LONG tile_offset_x = VIRTUAL_OFFSET + tile_x * surface.tile_width;
LONG tile_offset_y = VIRTUAL_OFFSET + tile_y * surface.tile_height;
update_rect.left += tile_offset_x;
update_rect.top += tile_offset_y;
update_rect.right += tile_offset_x;
update_rect.bottom += tile_offset_y;
hr = surface.pVirtualSurface->BeginDraw(
&update_rect, __uuidof(ID3D11Texture2D), (void**)&pTexture, &offset);
window->pCurrentSurface = surface.pVirtualSurface;
#else
hr = tile.pSurface->BeginDraw(&update_rect, __uuidof(ID3D11Texture2D),
(void**)&pTexture, &offset);
window->pCurrentSurface = tile.pSurface;
#endif
// DC includes the origin of the dirty / update rect in the draw offset,
// undo that here since WR expects it to be an absolute offset.
assert(SUCCEEDED(hr));
offset.x -= dirty_x0;
offset.y -= dirty_y0;
pTexture->GetDesc(&desc);
*x_offset = offset.x;
*y_offset = offset.y;
// Construct an EGLImage wrapper around the D3D texture for ANGLE.
const EGLAttrib attribs[] = {EGL_NONE};
window->mEGLImage = eglCreateImage(
window->EGLDisplay, EGL_NO_CONTEXT, EGL_D3D11_TEXTURE_ANGLE,
static_cast<EGLClientBuffer>(pTexture), attribs);
// Get the current FBO and RBO id, so we can restore them later
GLint currentFboId, currentRboId;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &currentFboId);
glGetIntegerv(GL_RENDERBUFFER_BINDING, &currentRboId);
// Create a render buffer object that is backed by the EGL image.
glGenRenderbuffers(1, &window->mColorRBO);
glBindRenderbuffer(GL_RENDERBUFFER, window->mColorRBO);
glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, window->mEGLImage);
// Get or create an FBO for the specified dimensions
GLuint fboId = GetOrCreateFbo(window, desc.Width, desc.Height);
// Attach the new renderbuffer to the FBO
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, window->mColorRBO);
// Restore previous FBO and RBO bindings
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFboId);
glBindRenderbuffer(GL_RENDERBUFFER, currentRboId);
return fboId;
}
// Unbind a currently bound DC surface
void com_dc_unbind_surface(Window* window) {
HRESULT hr = window->pCurrentSurface->EndDraw();
assert(SUCCEEDED(hr));
glDeleteRenderbuffers(1, &window->mColorRBO);
window->mColorRBO = 0;
eglDestroyImage(window->EGLDisplay, window->mEGLImage);
window->mEGLImage = EGL_NO_IMAGE;
}
void com_dc_begin_transaction(Window*) {}
// Add a DC surface to the visual tree. Called per-frame to build the
// composition.
void com_dc_add_surface(Window* window, uint64_t id, int x, int y, int clip_x,
int clip_y, int clip_w, int clip_h) {
Surface surface = window->surfaces[id];
window->mCurrentLayers.push_back(id);
// Place the visual - this changes frame to frame based on scroll position
// of the slice.
float offset_x = (float)(x + window->client_rect.left);
float offset_y = (float)(y + window->client_rect.top);
#ifdef USE_VIRTUAL_SURFACES
offset_x -= VIRTUAL_OFFSET;
offset_y -= VIRTUAL_OFFSET;
#endif
surface.pVisual->SetOffsetX(offset_x);
surface.pVisual->SetOffsetY(offset_y);
// Set the clip rect - converting from world space to the pre-offset space
// that DC requires for rectangle clips.
D2D_RECT_F clip_rect;
clip_rect.left = clip_x - offset_x;
clip_rect.top = clip_y - offset_y;
clip_rect.right = clip_rect.left + clip_w;
clip_rect.bottom = clip_rect.top + clip_h;
surface.pVisual->SetClip(clip_rect);
}
// Finish the composition transaction, telling DC to composite
void com_dc_end_transaction(Window* window) {
bool same = window->mPrevLayers == window->mCurrentLayers;
if (!same) {
HRESULT hr = window->pRoot->RemoveAllVisuals();
assert(SUCCEEDED(hr));
for (auto it = window->mCurrentLayers.begin();
it != window->mCurrentLayers.end(); ++it) {
Surface& surface = window->surfaces[*it];
// Add this visual as the last element in the visual tree (z-order is
// implicit, based on the order tiles are added).
hr = window->pRoot->AddVisual(surface.pVisual, FALSE, NULL);
assert(SUCCEEDED(hr));
}
}
window->mPrevLayers.swap(window->mCurrentLayers);
window->mCurrentLayers.clear();
HRESULT hr = window->pDCompDevice->Commit();
assert(SUCCEEDED(hr));
}
// Get a pointer to an EGL symbol
void* com_dc_get_proc_address(const char* name) {
return eglGetProcAddress(name);
}
}

View file

@ -1,267 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::os::raw::{c_void, c_char};
/*
This is a very simple (and unsafe!) rust wrapper for the DirectComposite / D3D11 / ANGLE
implementation in lib.cpp.
It just proxies the calls from the Compositor impl to the C99 code. This is very
hacky and not suitable for production!
*/
// Opaque wrapper for the Window type in lib.cpp
#[repr(C)]
pub struct Window {
_unused: [u8; 0]
}
// C99 functions that do the compositor work
extern {
fn com_dc_create_window(
width: i32,
height: i32,
enable_compositor: bool,
sync_mode: i32,
) -> *mut Window;
fn com_dc_destroy_window(window: *mut Window);
fn com_dc_tick(window: *mut Window) -> bool;
fn com_dc_get_proc_address(name: *const c_char) -> *const c_void;
fn com_dc_swap_buffers(window: *mut Window);
fn com_dc_create_surface(
window: *mut Window,
id: u64,
tile_width: i32,
tile_height: i32,
is_opaque: bool,
);
fn com_dc_create_tile(
window: *mut Window,
id: u64,
x: i32,
y: i32,
);
fn com_dc_destroy_tile(
window: *mut Window,
id: u64,
x: i32,
y: i32,
);
fn com_dc_destroy_surface(
window: *mut Window,
id: u64,
);
fn com_dc_bind_surface(
window: *mut Window,
surface_id: u64,
tile_x: i32,
tile_y: i32,
x_offset: &mut i32,
y_offset: &mut i32,
dirty_x0: i32,
dirty_y0: i32,
dirty_width: i32,
dirty_height: i32,
) -> u32;
fn com_dc_unbind_surface(window: *mut Window);
fn com_dc_begin_transaction(window: *mut Window);
fn com_dc_add_surface(
window: *mut Window,
id: u64,
x: i32,
y: i32,
clip_x: i32,
clip_y: i32,
clip_w: i32,
clip_h: i32,
);
fn com_dc_end_transaction(window: *mut Window);
fn deinit(window: *mut Window);
}
pub fn create_window(
width: i32,
height: i32,
enable_compositor: bool,
sync_mode: i32,
) -> *mut Window {
unsafe {
com_dc_create_window(width, height, enable_compositor, sync_mode)
}
}
pub fn destroy_window(window: *mut Window) {
unsafe {
com_dc_destroy_window(window);
}
}
pub fn tick(window: *mut Window) -> bool {
unsafe {
com_dc_tick(window)
}
}
pub fn get_proc_address(name: *const c_char) -> *const c_void {
unsafe {
com_dc_get_proc_address(name)
}
}
pub fn create_surface(
window: *mut Window,
id: u64,
tile_width: i32,
tile_height: i32,
is_opaque: bool,
) {
unsafe {
com_dc_create_surface(
window,
id,
tile_width,
tile_height,
is_opaque,
)
}
}
pub fn create_tile(
window: *mut Window,
id: u64,
x: i32,
y: i32,
) {
unsafe {
com_dc_create_tile(
window,
id,
x,
y,
)
}
}
pub fn destroy_tile(
window: *mut Window,
id: u64,
x: i32,
y: i32,
) {
unsafe {
com_dc_destroy_tile(
window,
id,
x,
y,
)
}
}
pub fn destroy_surface(
window: *mut Window,
id: u64,
) {
unsafe {
com_dc_destroy_surface(
window,
id,
)
}
}
pub fn bind_surface(
window: *mut Window,
surface_id: u64,
tile_x: i32,
tile_y: i32,
dirty_x0: i32,
dirty_y0: i32,
dirty_width: i32,
dirty_height: i32,
) -> (u32, i32, i32) {
unsafe {
let mut x_offset = 0;
let mut y_offset = 0;
let fbo_id = com_dc_bind_surface(
window,
surface_id,
tile_x,
tile_y,
&mut x_offset,
&mut y_offset,
dirty_x0,
dirty_y0,
dirty_width,
dirty_height,
);
(fbo_id, x_offset, y_offset)
}
}
pub fn add_surface(
window: *mut Window,
id: u64,
x: i32,
y: i32,
clip_x: i32,
clip_y: i32,
clip_w: i32,
clip_h: i32,
) {
unsafe {
com_dc_add_surface(
window,
id,
x,
y,
clip_x,
clip_y,
clip_w,
clip_h,
)
}
}
pub fn begin_transaction(window: *mut Window) {
unsafe {
com_dc_begin_transaction(window)
}
}
pub fn unbind_surface(window: *mut Window) {
unsafe {
com_dc_unbind_surface(window)
}
}
pub fn end_transaction(window: *mut Window) {
unsafe {
com_dc_end_transaction(window)
}
}
pub fn swap_buffers(window: *mut Window) {
unsafe {
com_dc_swap_buffers(window);
}
}
pub fn deinit(window: *mut Window) {
todo!()
}

View file

@ -1,16 +0,0 @@
[package]
name = "compositor"
version = "0.1.0"
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
edition = "2018"
license = "MPL-2.0"
[dependencies]
webrender = { path = "../../webrender" }
gleam = "0.15"
[target.'cfg(windows)'.dependencies]
compositor-windows = { path = "../compositor-windows" }
[target.'cfg(target_os = "linux")'.dependencies]
compositor-wayland = { path = "../compositor-wayland" }

View file

@ -1,555 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
An example of how to implement the Compositor trait that
allows picture caching surfaces to be composited by the operating
system.
The current example supports DirectComposite on Windows only.
*/
use euclid::Angle;
use gleam::gl;
use std::ffi::CString;
use std::sync::mpsc;
use webrender::{CompositorSurfaceTransform, Transaction, api::*, euclid::point2};
use webrender::api::units::*;
#[cfg(target_os = "windows")]
use compositor_windows as compositor;
#[cfg(target_os = "linux")]
use compositor_wayland as compositor;
use std::{env, f32, process};
// A very hacky integration with DirectComposite. It proxies calls from the compositor
// interface to a simple C99 library which does the DirectComposition / D3D11 / ANGLE
// interfacing. This is a very unsafe impl due to the way the window pointer is passed
// around!
struct DirectCompositeInterface {
window: *mut compositor::Window,
}
impl DirectCompositeInterface {
fn new(window: *mut compositor::Window) -> Self {
DirectCompositeInterface {
window,
}
}
}
impl webrender::Compositor for DirectCompositeInterface {
fn create_surface(
&mut self,
id: webrender::NativeSurfaceId,
_virtual_offset: DeviceIntPoint,
tile_size: DeviceIntSize,
is_opaque: bool,
) {
compositor::create_surface(
self.window,
id.0,
tile_size.width,
tile_size.height,
is_opaque,
);
}
fn destroy_surface(
&mut self,
id: webrender::NativeSurfaceId,
) {
compositor::destroy_surface(self.window, id.0);
}
fn create_tile(
&mut self,
id: webrender::NativeTileId,
) {
compositor::create_tile(
self.window,
id.surface_id.0,
id.x,
id.y,
);
}
fn destroy_tile(
&mut self,
id: webrender::NativeTileId,
) {
compositor::destroy_tile(
self.window,
id.surface_id.0,
id.x,
id.y,
);
}
fn bind(
&mut self,
id: webrender::NativeTileId,
dirty_rect: DeviceIntRect,
_valid_rect: DeviceIntRect,
) -> webrender::NativeSurfaceInfo {
let (fbo_id, x, y) = compositor::bind_surface(
self.window,
id.surface_id.0,
id.x,
id.y,
dirty_rect.origin.x,
dirty_rect.origin.y,
dirty_rect.size.width,
dirty_rect.size.height,
);
webrender::NativeSurfaceInfo {
origin: DeviceIntPoint::new(x, y),
fbo_id,
}
}
fn unbind(&mut self) {
compositor::unbind_surface(self.window);
}
fn begin_frame(&mut self) {
compositor::begin_transaction(self.window);
}
fn add_surface(
&mut self,
id: webrender::NativeSurfaceId,
transform: CompositorSurfaceTransform,
clip_rect: DeviceIntRect,
_image_rendering: ImageRendering,
) {
compositor::add_surface(
self.window,
id.0,
transform.transform_point2d(point2(0., 0.)).unwrap().x as i32,
transform.transform_point2d(point2(0., 0.)).unwrap().y as i32,
clip_rect.origin.x,
clip_rect.origin.y,
clip_rect.size.width,
clip_rect.size.height,
);
}
fn end_frame(&mut self) {
compositor::end_transaction(self.window);
}
fn create_external_surface(&mut self, _: webrender::NativeSurfaceId, _: bool) { todo!() }
fn attach_external_image(
&mut self,
_id: webrender::NativeSurfaceId,
_external_image: ExternalImageId
) {
todo!()
}
fn enable_native_compositor(&mut self, _enable: bool) {
todo!()
}
fn deinit(&mut self) {
compositor::deinit(self.window);
}
fn get_capabilities(&self) -> webrender::CompositorCapabilities {
webrender::CompositorCapabilities {
virtual_surface_size: 1024 * 1024,
..Default::default()
}
}
fn invalidate_tile(
&mut self,
_id: webrender::NativeTileId,
_valid_rect: DeviceIntRect,
) {}
fn start_compositing(
&mut self,
_dirty_rects: &[DeviceIntRect],
_opaque_rects: &[DeviceIntRect],
) {}
}
// Simplisitic implementation of the WR notifier interface to know when a frame
// has been prepared and can be rendered.
struct Notifier {
tx: mpsc::Sender<()>,
}
impl Notifier {
fn new(tx: mpsc::Sender<()>) -> Self {
Notifier {
tx,
}
}
}
impl RenderNotifier for Notifier {
fn clone(&self) -> Box<dyn RenderNotifier> {
Box::new(Notifier {
tx: self.tx.clone()
})
}
fn wake_up(&self, _composite_needed: bool) {
}
fn new_frame_ready(&self,
_: DocumentId,
_scrolled: bool,
_composite_needed: bool,
_render_time: Option<u64>) {
self.tx.send(()).ok();
}
}
fn push_rotated_rect(
builder: &mut DisplayListBuilder,
rect: LayoutRect,
color: ColorF,
spatial_id: SpatialId,
root_pipeline_id: PipelineId,
angle: f32,
time: f32,
) {
let color = color.scale_rgb(time);
let rotation = LayoutTransform::rotation(
0.0,
0.0,
1.0,
Angle::radians(2.0 * std::f32::consts::PI * angle),
);
let transform_origin = LayoutVector3D::new(
rect.origin.x + rect.size.width * 0.5,
rect.origin.y + rect.size.height * 0.5,
0.0,
);
let transform = rotation
.pre_translate(-transform_origin)
.then_translate(transform_origin);
let spatial_id = builder.push_reference_frame(
LayoutPoint::zero(),
spatial_id,
TransformStyle::Flat,
PropertyBinding::Value(transform),
ReferenceFrameKind::Transform {
is_2d_scale_translation: false,
should_snap: false,
},
);
builder.push_rect(
&CommonItemProperties::new(
rect,
SpaceAndClipInfo {
spatial_id,
clip_id: ClipId::root(root_pipeline_id),
},
),
rect,
color,
);
}
fn build_display_list(
builder: &mut DisplayListBuilder,
scroll_id: ExternalScrollId,
root_pipeline_id: PipelineId,
layout_size: LayoutSize,
time: f32,
invalidations: Invalidations,
) {
let size_factor = match invalidations {
Invalidations::Small => 0.1,
Invalidations::Large | Invalidations::Scrolling => 1.0,
};
let fixed_space_info = SpaceAndClipInfo {
spatial_id: SpatialId::root_scroll_node(root_pipeline_id),
clip_id: ClipId::root(root_pipeline_id),
};
let scroll_space_info = builder.define_scroll_frame(
&fixed_space_info,
scroll_id,
LayoutRect::new(LayoutPoint::zero(), layout_size),
LayoutRect::new(LayoutPoint::zero(), layout_size),
ScrollSensitivity::Script,
LayoutVector2D::zero(),
);
builder.push_rect(
&CommonItemProperties::new(
LayoutRect::new(LayoutPoint::zero(), layout_size).inflate(-10.0, -10.0),
fixed_space_info,
),
LayoutRect::new(LayoutPoint::zero(), layout_size).inflate(-10.0, -10.0),
ColorF::new(0.8, 0.8, 0.8, 1.0),
);
push_rotated_rect(
builder,
LayoutRect::new(
LayoutPoint::new(100.0, 100.0),
LayoutSize::new(size_factor * 400.0, size_factor * 400.0),
),
ColorF::new(1.0, 0.0, 0.0, 1.0),
scroll_space_info.spatial_id,
root_pipeline_id,
time,
time,
);
push_rotated_rect(
builder,
LayoutRect::new(
LayoutPoint::new(800.0, 100.0),
LayoutSize::new(size_factor * 100.0, size_factor * 600.0),
),
ColorF::new(0.0, 1.0, 0.0, 1.0),
fixed_space_info.spatial_id,
root_pipeline_id,
0.2,
time,
);
push_rotated_rect(
builder,
LayoutRect::new(
LayoutPoint::new(700.0, 200.0),
LayoutSize::new(size_factor * 300.0, size_factor * 300.0),
),
ColorF::new(0.0, 0.0, 1.0, 1.0),
scroll_space_info.spatial_id,
root_pipeline_id,
0.1,
time,
);
push_rotated_rect(
builder,
LayoutRect::new(
LayoutPoint::new(100.0, 600.0),
LayoutSize::new(size_factor * 400.0, size_factor * 400.0),
),
ColorF::new(1.0, 1.0, 0.0, 1.0),
scroll_space_info.spatial_id,
root_pipeline_id,
time,
time,
);
push_rotated_rect(
builder,
LayoutRect::new(
LayoutPoint::new(700.0, 600.0),
LayoutSize::new(size_factor * 400.0, size_factor * 400.0),
),
ColorF::new(0.0, 1.0, 1.0, 1.0),
scroll_space_info.spatial_id,
root_pipeline_id,
time,
time,
);
}
#[derive(Debug, Copy, Clone)]
enum Invalidations {
Large,
Small,
Scrolling,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
enum Sync {
None = 0,
Swap = 1,
Commit = 2,
Flush = 3,
Query = 4,
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 6 {
println!("USAGE: compositor [native|none] [small|large|scroll] [none|swap|commit|flush|query] width height");
process::exit(0);
}
let enable_compositor = match args[1].parse::<String>().unwrap().as_str() {
"native" => true,
"none" => false,
_ => panic!("invalid compositor [native, none]"),
};
let inv_mode = match args[2].parse::<String>().unwrap().as_str() {
"small" => Invalidations::Small,
"large" => Invalidations::Large,
"scroll" => Invalidations::Scrolling,
_ => panic!("invalid invalidations [small, large, scroll]"),
};
let sync_mode = match args[3].parse::<String>().unwrap().as_str() {
"none" => Sync::None,
"swap" => Sync::Swap,
"commit" => Sync::Commit,
"flush" => Sync::Flush,
"query" => Sync::Query,
_ => panic!("invalid sync mode [none, swap, commit, flush, query]"),
};
let width = args[4].parse().unwrap();
let height = args[5].parse().unwrap();
let device_size = DeviceIntSize::new(width, height);
// Load GL, construct WR and the native compositor interface.
let window = compositor::create_window(
device_size.width,
device_size.height,
enable_compositor,
sync_mode as i32,
);
let debug_flags = DebugFlags::empty();
let compositor_config = if enable_compositor {
webrender::CompositorConfig::Native {
max_update_rects: 1,
compositor: Box::new(DirectCompositeInterface::new(window)),
}
} else {
webrender::CompositorConfig::Draw {
max_partial_present_rects: 0,
draw_previous_partial_present_regions: false,
partial_present: None,
}
};
let opts = webrender::RendererOptions {
clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
debug_flags,
compositor_config,
surface_origin_is_top_left: false,
..webrender::RendererOptions::default()
};
let (tx, rx) = mpsc::channel();
let notifier = Box::new(Notifier::new(tx));
let gl = unsafe {
gl::GlesFns::load_with(
|symbol| {
let symbol = CString::new(symbol).unwrap();
let ptr = compositor::get_proc_address(symbol.as_ptr());
ptr
}
)
};
let (mut renderer, sender) = webrender::Renderer::new(
gl.clone(),
notifier,
opts,
None,
).unwrap();
let mut api = sender.create_api();
let document_id = api.add_document(device_size);
let device_pixel_ratio = 1.0;
let mut current_epoch = Epoch(0);
let root_pipeline_id = PipelineId(0, 0);
let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio);
let mut time = 0.0;
let scroll_id = ExternalScrollId(3, root_pipeline_id);
// Kick off first transaction which will mean we get a notify below to build the DL and render.
let mut txn = Transaction::new();
txn.set_root_pipeline(root_pipeline_id);
if let Invalidations::Scrolling = inv_mode {
let mut root_builder = DisplayListBuilder::new(root_pipeline_id);
build_display_list(
&mut root_builder,
scroll_id,
root_pipeline_id,
layout_size,
1.0,
inv_mode,
);
txn.set_display_list(
current_epoch,
None,
layout_size,
root_builder.finalize(),
true,
);
}
txn.generate_frame(0);
api.send_transaction(document_id, txn);
// Tick the compositor (in this sample, we don't block on UI events)
while compositor::tick(window) {
// If there is a new frame ready to draw
if let Ok(..) = rx.try_recv() {
// Update and render. This will invoke the native compositor interface implemented above
// as required.
renderer.update();
renderer.render(device_size, 0).unwrap();
let _ = renderer.flush_pipeline_info();
// Construct a simple display list that can be drawn and composited by DC.
let mut txn = Transaction::new();
match inv_mode {
Invalidations::Small | Invalidations::Large => {
let mut root_builder = DisplayListBuilder::new(root_pipeline_id);
build_display_list(
&mut root_builder,
scroll_id,
root_pipeline_id,
layout_size,
time,
inv_mode,
);
txn.set_display_list(
current_epoch,
None,
layout_size,
root_builder.finalize(),
true,
);
}
Invalidations::Scrolling => {
let d = 0.5 - 0.5 * (2.0 * f32::consts::PI * 5.0 * time).cos();
txn.scroll_node_with_id(
LayoutPoint::new(0.0, (d * 100.0).round()),
scroll_id,
ScrollClamping::NoClamping,
);
}
}
txn.generate_frame(0);
api.send_transaction(document_id, txn);
current_epoch.0 += 1;
time += 0.001;
if time > 1.0 {
time = 0.0;
}
// This does nothing when native compositor is enabled
compositor::swap_buffers(window);
}
}
renderer.deinit();
compositor::destroy_window(window);
}

View file

@ -1,67 +0,0 @@
[package]
name = "webrender-examples"
version = "0.1.0"
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
license = "MPL-2.0"
repository = "https://github.com/servo/webrender"
edition = "2018"
[[bin]]
name = "alpha_perf"
path = "alpha_perf.rs"
[[bin]]
name = "animation"
path = "animation.rs"
[[bin]]
name = "basic"
path = "basic.rs"
[[bin]]
name = "blob"
path = "blob.rs"
[[bin]]
name = "document"
path = "document.rs"
[[bin]]
name = "iframe"
path = "iframe.rs"
[[bin]]
name = "image_resize"
path = "image_resize.rs"
[[bin]]
name = "multiwindow"
path = "multiwindow.rs"
[[bin]]
name = "scrolling"
path = "scrolling.rs"
[[bin]]
name = "texture_cache_stress"
path = "texture_cache_stress.rs"
[[bin]]
name = "yuv"
path = "yuv.rs"
[features]
debug = ["webrender/capture", "webrender/profiler"]
[dependencies]
app_units = "0.7"
env_logger = "0.5"
euclid = "0.22"
gleam = "0.15"
glutin = "0.21"
rayon = "1"
webrender = { path = "../webrender" }
winit = "0.19"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.7"

View file

@ -1,8 +0,0 @@
# Examples
This directory contains a collection of examples which uses the WebRender API.
To run an example e.g. `basic`, try:
```
cargo run --bin basic
```

View file

@ -1,94 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use std::cmp;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::DeviceIntSize;
struct App {
rect_count: usize,
}
impl Example for App {
fn render(
&mut self,
_api: &mut RenderApi,
builder: &mut DisplayListBuilder,
_txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let bounds = (0, 0).to(1920, 1080);
let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
builder.push_simple_stacking_context(
bounds.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
for _ in 0 .. self.rect_count {
builder.push_rect(
&CommonItemProperties::new(bounds, space_and_clip),
bounds,
ColorF::new(1.0, 1.0, 1.0, 0.05)
);
}
builder.pop_stacking_context();
}
fn on_event(
&mut self,
event: winit::WindowEvent,
_api: &mut RenderApi,
_document_id: DocumentId
) -> bool {
match event {
winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Pressed,
virtual_keycode: Some(key),
..
},
..
} => {
match key {
winit::VirtualKeyCode::Right => {
self.rect_count += 1;
println!("rects = {}", self.rect_count);
}
winit::VirtualKeyCode::Left => {
self.rect_count = cmp::max(self.rect_count, 1) - 1;
println!("rects = {}", self.rect_count);
}
_ => {}
};
}
_ => (),
}
true
}
}
fn main() {
let mut app = App {
rect_count: 1,
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -1,223 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! This example creates a 200x200 white rect and allows the user to move it
//! around by using the arrow keys and rotate with '<'/'>'.
//! It does this by using the animation API.
//! The example also features seamless opaque/transparent split of a
//! rounded cornered rectangle, which is done automatically during the
//! scene building for render optimization.
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use euclid::Angle;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
struct App {
property_key0: PropertyBindingKey<LayoutTransform>,
property_key1: PropertyBindingKey<LayoutTransform>,
property_key2: PropertyBindingKey<LayoutTransform>,
opacity_key: PropertyBindingKey<f32>,
opacity: f32,
angle0: f32,
angle1: f32,
angle2: f32,
}
impl App {
fn add_rounded_rect(
&mut self,
bounds: LayoutRect,
color: ColorF,
builder: &mut DisplayListBuilder,
pipeline_id: PipelineId,
property_key: PropertyBindingKey<LayoutTransform>,
opacity_key: Option<PropertyBindingKey<f32>>,
) {
let filters = match opacity_key {
Some(opacity_key) => {
vec![
FilterOp::Opacity(PropertyBinding::Binding(opacity_key, self.opacity), self.opacity),
]
}
None => {
vec![]
}
};
let spatial_id = builder.push_reference_frame(
bounds.origin,
SpatialId::root_scroll_node(pipeline_id),
TransformStyle::Flat,
PropertyBinding::Binding(property_key, LayoutTransform::identity()),
ReferenceFrameKind::Transform {
is_2d_scale_translation: false,
should_snap: false,
},
);
builder.push_simple_stacking_context_with_filters(
LayoutPoint::zero(),
spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
&filters,
&[],
&[]
);
let space_and_clip = SpaceAndClipInfo {
spatial_id,
clip_id: ClipId::root(pipeline_id),
};
let clip_bounds = LayoutRect::new(LayoutPoint::zero(), bounds.size);
let complex_clip = ComplexClipRegion {
rect: clip_bounds,
radii: BorderRadius::uniform(30.0),
mode: ClipMode::Clip,
};
let clip_id = builder.define_clip_rounded_rect(
&space_and_clip,
complex_clip,
);
// Fill it with a white rect
builder.push_rect(
&CommonItemProperties::new(
LayoutRect::new(LayoutPoint::zero(), bounds.size),
SpaceAndClipInfo {
spatial_id,
clip_id,
}
),
LayoutRect::new(LayoutPoint::zero(), bounds.size),
color,
);
builder.pop_stacking_context();
builder.pop_reference_frame();
}
}
impl Example for App {
const WIDTH: u32 = 2048;
const HEIGHT: u32 = 1536;
fn render(
&mut self,
_api: &mut RenderApi,
builder: &mut DisplayListBuilder,
_txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let opacity_key = self.opacity_key;
let bounds = (150, 150).to(250, 250);
let key0 = self.property_key0;
self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, pipeline_id, key0, Some(opacity_key));
let bounds = (400, 400).to(600, 600);
let key1 = self.property_key1;
self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, pipeline_id, key1, None);
let bounds = (200, 500).to(350, 580);
let key2 = self.property_key2;
self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, pipeline_id, key2, None);
}
fn on_event(&mut self, win_event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool {
let mut rebuild_display_list = false;
match win_event {
winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Pressed,
virtual_keycode: Some(key),
..
},
..
} => {
let (delta_angle, delta_opacity) = match key {
winit::VirtualKeyCode::Down => (0.0, -0.1),
winit::VirtualKeyCode::Up => (0.0, 0.1),
winit::VirtualKeyCode::Right => (1.0, 0.0),
winit::VirtualKeyCode::Left => (-1.0, 0.0),
winit::VirtualKeyCode::R => {
rebuild_display_list = true;
(0.0, 0.0)
}
_ => return false,
};
// Update the transform based on the keyboard input and push it to
// webrender using the generate_frame API. This will recomposite with
// the updated transform.
self.opacity += delta_opacity;
self.angle0 += delta_angle * 0.1;
self.angle1 += delta_angle * 0.2;
self.angle2 -= delta_angle * 0.15;
let xf0 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle0));
let xf1 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle1));
let xf2 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle2));
let mut txn = Transaction::new();
txn.update_dynamic_properties(
DynamicProperties {
transforms: vec![
PropertyValue {
key: self.property_key0,
value: xf0,
},
PropertyValue {
key: self.property_key1,
value: xf1,
},
PropertyValue {
key: self.property_key2,
value: xf2,
},
],
floats: vec![
PropertyValue {
key: self.opacity_key,
value: self.opacity,
}
],
colors: vec![],
},
);
txn.generate_frame(0);
api.send_transaction(document_id, txn);
}
_ => (),
}
rebuild_display_list
}
}
fn main() {
let mut app = App {
property_key0: PropertyBindingKey::new(42), // arbitrary magic number
property_key1: PropertyBindingKey::new(44), // arbitrary magic number
property_key2: PropertyBindingKey::new(45), // arbitrary magic number
opacity_key: PropertyBindingKey::new(43),
opacity: 0.5,
angle0: 0.0,
angle1: 0.0,
angle2: 0.0,
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -1,324 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use euclid::vec2;
use winit::TouchPhase;
use std::collections::HashMap;
use webrender::ShaderPrecacheFlags;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
#[derive(Debug)]
enum Gesture {
None,
Pan,
Zoom,
}
#[derive(Debug)]
struct Touch {
id: u64,
start_x: f32,
start_y: f32,
current_x: f32,
current_y: f32,
}
fn dist(x0: f32, y0: f32, x1: f32, y1: f32) -> f32 {
let dx = x0 - x1;
let dy = y0 - y1;
((dx * dx) + (dy * dy)).sqrt()
}
impl Touch {
fn distance_from_start(&self) -> f32 {
dist(self.start_x, self.start_y, self.current_x, self.current_y)
}
fn initial_distance_from_other(&self, other: &Touch) -> f32 {
dist(self.start_x, self.start_y, other.start_x, other.start_y)
}
fn current_distance_from_other(&self, other: &Touch) -> f32 {
dist(
self.current_x,
self.current_y,
other.current_x,
other.current_y,
)
}
}
struct TouchState {
active_touches: HashMap<u64, Touch>,
current_gesture: Gesture,
start_zoom: f32,
current_zoom: f32,
start_pan: DeviceIntPoint,
current_pan: DeviceIntPoint,
}
enum TouchResult {
None,
Pan(DeviceIntPoint),
Zoom(f32),
}
impl TouchState {
fn new() -> TouchState {
TouchState {
active_touches: HashMap::new(),
current_gesture: Gesture::None,
start_zoom: 1.0,
current_zoom: 1.0,
start_pan: DeviceIntPoint::zero(),
current_pan: DeviceIntPoint::zero(),
}
}
fn handle_event(&mut self, touch: winit::Touch) -> TouchResult {
match touch.phase {
TouchPhase::Started => {
debug_assert!(!self.active_touches.contains_key(&touch.id));
self.active_touches.insert(
touch.id,
Touch {
id: touch.id,
start_x: touch.location.x as f32,
start_y: touch.location.y as f32,
current_x: touch.location.x as f32,
current_y: touch.location.y as f32,
},
);
self.current_gesture = Gesture::None;
}
TouchPhase::Moved => {
match self.active_touches.get_mut(&touch.id) {
Some(active_touch) => {
active_touch.current_x = touch.location.x as f32;
active_touch.current_y = touch.location.y as f32;
}
None => panic!("move touch event with unknown touch id!"),
}
match self.current_gesture {
Gesture::None => {
let mut over_threshold_count = 0;
let active_touch_count = self.active_touches.len();
for (_, touch) in &self.active_touches {
if touch.distance_from_start() > 8.0 {
over_threshold_count += 1;
}
}
if active_touch_count == over_threshold_count {
if active_touch_count == 1 {
self.start_pan = self.current_pan;
self.current_gesture = Gesture::Pan;
} else if active_touch_count == 2 {
self.start_zoom = self.current_zoom;
self.current_gesture = Gesture::Zoom;
}
}
}
Gesture::Pan => {
let keys: Vec<u64> = self.active_touches.keys().cloned().collect();
debug_assert!(keys.len() == 1);
let active_touch = &self.active_touches[&keys[0]];
let x = active_touch.current_x - active_touch.start_x;
let y = active_touch.current_y - active_touch.start_y;
self.current_pan.x = self.start_pan.x + x.round() as i32;
self.current_pan.y = self.start_pan.y + y.round() as i32;
return TouchResult::Pan(self.current_pan);
}
Gesture::Zoom => {
let keys: Vec<u64> = self.active_touches.keys().cloned().collect();
debug_assert!(keys.len() == 2);
let touch0 = &self.active_touches[&keys[0]];
let touch1 = &self.active_touches[&keys[1]];
let initial_distance = touch0.initial_distance_from_other(touch1);
let current_distance = touch0.current_distance_from_other(touch1);
self.current_zoom = self.start_zoom * current_distance / initial_distance;
return TouchResult::Zoom(self.current_zoom);
}
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
self.active_touches.remove(&touch.id).unwrap();
self.current_gesture = Gesture::None;
}
}
TouchResult::None
}
}
fn main() {
let mut app = App {
touch_state: TouchState::new(),
};
boilerplate::main_wrapper(&mut app, None);
}
struct App {
touch_state: TouchState,
}
impl Example for App {
// Make this the only example to test all shaders for compile errors.
const PRECACHE_SHADER_FLAGS: ShaderPrecacheFlags = ShaderPrecacheFlags::FULL_COMPILE;
fn render(
&mut self,
api: &mut RenderApi,
builder: &mut DisplayListBuilder,
txn: &mut Transaction,
_: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let content_bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(800.0, 600.0));
let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
let spatial_id = root_space_and_clip.spatial_id;
builder.push_simple_stacking_context(
content_bounds.origin,
spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
let image_mask_key = api.generate_image_key();
txn.add_image(
image_mask_key,
ImageDescriptor::new(2, 2, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::new(vec![0, 80, 180, 255]),
None,
);
let mask = ImageMask {
image: image_mask_key,
rect: (75, 75).by(100, 100),
repeat: false,
};
let complex = ComplexClipRegion::new(
(50, 50).to(150, 150),
BorderRadius::uniform(20.0),
ClipMode::Clip
);
let mask_clip_id = builder.define_clip_image_mask(
&root_space_and_clip,
mask,
&vec![],
FillRule::Nonzero,
);
let clip_id = builder.define_clip_rounded_rect(
&SpaceAndClipInfo {
spatial_id: root_space_and_clip.spatial_id,
clip_id: mask_clip_id,
},
complex,
);
builder.push_rect(
&CommonItemProperties::new(
(100, 100).to(200, 200),
SpaceAndClipInfo { spatial_id, clip_id },
),
(100, 100).to(200, 200),
ColorF::new(0.0, 1.0, 0.0, 1.0),
);
builder.push_rect(
&CommonItemProperties::new(
(250, 100).to(350, 200),
SpaceAndClipInfo { spatial_id, clip_id },
),
(250, 100).to(350, 200),
ColorF::new(0.0, 1.0, 0.0, 1.0),
);
let border_side = BorderSide {
color: ColorF::new(0.0, 0.0, 1.0, 1.0),
style: BorderStyle::Groove,
};
let border_widths = LayoutSideOffsets::new_all_same(10.0);
let border_details = BorderDetails::Normal(NormalBorder {
top: border_side,
right: border_side,
bottom: border_side,
left: border_side,
radius: BorderRadius::uniform(20.0),
do_aa: true,
});
let bounds = (100, 100).to(200, 200);
builder.push_border(
&CommonItemProperties::new(
bounds,
SpaceAndClipInfo { spatial_id, clip_id },
),
bounds,
border_widths,
border_details,
);
if false {
// draw box shadow?
let simple_box_bounds = (20, 200).by(50, 50);
let offset = vec2(10.0, 10.0);
let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
let blur_radius = 0.0;
let spread_radius = 0.0;
let simple_border_radius = 8.0;
let box_shadow_type = BoxShadowClipMode::Inset;
builder.push_box_shadow(
&CommonItemProperties::new(content_bounds, root_space_and_clip),
simple_box_bounds,
offset,
color,
blur_radius,
spread_radius,
BorderRadius::uniform(simple_border_radius),
box_shadow_type,
);
}
builder.pop_stacking_context();
}
fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool {
let mut txn = Transaction::new();
match event {
winit::WindowEvent::Touch(touch) => match self.touch_state.handle_event(touch) {
TouchResult::Pan(pan) => {
txn.set_pan(pan);
}
TouchResult::Zoom(zoom) => {
txn.set_pinch_zoom(ZoomFactor::new(zoom));
}
TouchResult::None => {}
},
_ => (),
}
if !txn.is_empty() {
txn.generate_frame(0);
api.send_transaction(document_id, txn);
}
false
}
}

View file

@ -1,290 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate gleam;
extern crate glutin;
extern crate rayon;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use rayon::{ThreadPool, ThreadPoolBuilder};
use rayon::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;
use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, PrimitiveFlags};
use webrender::api::{ColorF, CommonItemProperties, SpaceAndClipInfo, ImageDescriptorFlags};
use webrender::api::units::*;
use webrender::render_api::*;
use webrender::euclid::size2;
// This example shows how to implement a very basic BlobImageHandler that can only render
// a checkerboard pattern.
// The deserialized command list internally used by this example is just a color.
type ImageRenderingCommands = api::ColorU;
// Serialize/deserialize the blob.
// For real usecases you should probably use serde rather than doing it by hand.
fn serialize_blob(color: api::ColorU) -> Arc<Vec<u8>> {
Arc::new(vec![color.r, color.g, color.b, color.a])
}
fn deserialize_blob(blob: &[u8]) -> Result<ImageRenderingCommands, ()> {
let mut iter = blob.iter();
return match (iter.next(), iter.next(), iter.next(), iter.next()) {
(Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(api::ColorU::new(r, g, b, a)),
(Some(&a), None, None, None) => Ok(api::ColorU::new(a, a, a, a)),
_ => Err(()),
};
}
// This is the function that applies the deserialized drawing commands and generates
// actual image data.
fn render_blob(
commands: Arc<ImageRenderingCommands>,
descriptor: &api::BlobImageDescriptor,
tile: TileOffset,
) -> api::BlobImageResult {
let color = *commands;
// Note: This implementation ignores the dirty rect which isn't incorrect
// but is a missed optimization.
// Allocate storage for the result. Right now the resource cache expects the
// tiles to have have no stride or offset.
let bpp = 4;
let mut texels = Vec::with_capacity((descriptor.rect.size.area() * bpp) as usize);
// Generate a per-tile pattern to see it in the demo. For a real use case it would not
// make sense for the rendered content to depend on its tile.
let tile_checker = (tile.x % 2 == 0) != (tile.y % 2 == 0);
let [w, h] = descriptor.rect.size.to_array();
let offset = descriptor.rect.origin;
for y in 0..h {
for x in 0..w {
// Apply the tile's offset. This is important: all drawing commands should be
// translated by this offset to give correct results with tiled blob images.
let x2 = x + offset.x;
let y2 = y + offset.y;
// Render a simple checkerboard pattern
let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) {
1
} else {
0
};
// ..nested in the per-tile checkerboard pattern
let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
match descriptor.format {
api::ImageFormat::BGRA8 => {
texels.push(color.b * checker + tc);
texels.push(color.g * checker + tc);
texels.push(color.r * checker + tc);
texels.push(color.a * checker + tc);
}
api::ImageFormat::R8 => {
texels.push(color.a * checker + tc);
}
_ => {
return Err(api::BlobImageError::Other(
format!("Unsupported image format"),
));
}
}
}
}
Ok(api::RasterizedBlobImage {
data: Arc::new(texels),
rasterized_rect: size2(w, h).into(),
})
}
struct CheckerboardRenderer {
// We are going to defer the rendering work to worker threads.
// Using a pre-built Arc<ThreadPool> rather than creating our own threads
// makes it possible to share the same thread pool as the glyph renderer (if we
// want to).
workers: Arc<ThreadPool>,
// The deserialized drawing commands.
// In this example we store them in Arcs. This isn't necessary since in this simplified
// case the command list is a simple 32 bits value and would be cheap to clone before sending
// to the workers. But in a more realistic scenario the commands would typically be bigger
// and more expensive to clone, so let's pretend it is also the case here.
image_cmds: HashMap<api::BlobImageKey, Arc<ImageRenderingCommands>>,
}
impl CheckerboardRenderer {
fn new(workers: Arc<ThreadPool>) -> Self {
CheckerboardRenderer {
image_cmds: HashMap::new(),
workers,
}
}
}
impl api::BlobImageHandler for CheckerboardRenderer {
fn create_similar(&self) -> Box<dyn api::BlobImageHandler> {
Box::new(CheckerboardRenderer::new(Arc::clone(&self.workers)))
}
fn add(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>,
_visible_rect: &DeviceIntRect, _: api::TileSize) {
self.image_cmds
.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
}
fn update(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>,
_visible_rect: &DeviceIntRect, _dirty_rect: &BlobDirtyRect) {
// Here, updating is just replacing the current version of the commands with
// the new one (no incremental updates).
self.image_cmds
.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
}
fn delete(&mut self, key: api::BlobImageKey) {
self.image_cmds.remove(&key);
}
fn prepare_resources(
&mut self,
_services: &dyn api::BlobImageResources,
_requests: &[api::BlobImageParams],
) {}
fn enable_multithreading(&mut self, _: bool) {}
fn delete_font(&mut self, _font: api::FontKey) {}
fn delete_font_instance(&mut self, _instance: api::FontInstanceKey) {}
fn clear_namespace(&mut self, _namespace: api::IdNamespace) {}
fn create_blob_rasterizer(&mut self) -> Box<dyn api::AsyncBlobImageRasterizer> {
Box::new(Rasterizer {
workers: Arc::clone(&self.workers),
image_cmds: self.image_cmds.clone(),
})
}
}
struct Rasterizer {
workers: Arc<ThreadPool>,
image_cmds: HashMap<api::BlobImageKey, Arc<ImageRenderingCommands>>,
}
impl api::AsyncBlobImageRasterizer for Rasterizer {
fn rasterize(
&mut self,
requests: &[api::BlobImageParams],
_low_priority: bool
) -> Vec<(api::BlobImageRequest, api::BlobImageResult)> {
let requests: Vec<(&api::BlobImageParams, Arc<ImageRenderingCommands>)> = requests.into_iter().map(|params| {
(params, Arc::clone(&self.image_cmds[&params.request.key]))
}).collect();
self.workers.install(|| {
requests.into_par_iter().map(|(params, commands)| {
(params.request, render_blob(commands, &params.descriptor, params.request.tile))
}).collect()
})
}
}
struct App {}
impl Example for App {
fn render(
&mut self,
api: &mut RenderApi,
builder: &mut DisplayListBuilder,
txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
builder.push_simple_stacking_context(
LayoutPoint::zero(),
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
let size1 = DeviceIntSize::new(500, 500);
let blob_img1 = api.generate_blob_image_key();
txn.add_blob_image(
blob_img1,
api::ImageDescriptor::new(
size1.width,
size1.height,
api::ImageFormat::BGRA8,
ImageDescriptorFlags::IS_OPAQUE,
),
serialize_blob(api::ColorU::new(50, 50, 150, 255)),
size1.into(),
Some(128),
);
let bounds = (30, 30).by(size1.width, size1.height);
builder.push_image(
&CommonItemProperties::new(bounds, space_and_clip),
bounds,
api::ImageRendering::Auto,
api::AlphaType::PremultipliedAlpha,
blob_img1.as_image(),
ColorF::WHITE,
);
let size2 = DeviceIntSize::new(256, 256);
let blob_img2 = api.generate_blob_image_key();
txn.add_blob_image(
blob_img2,
api::ImageDescriptor::new(
size2.width,
size2.height,
api::ImageFormat::BGRA8,
ImageDescriptorFlags::IS_OPAQUE,
),
serialize_blob(api::ColorU::new(50, 150, 50, 255)),
size2.into(),
None,
);
let bounds = (600, 600).by(size2.width, size2.height);
builder.push_image(
&CommonItemProperties::new(bounds, space_and_clip),
bounds,
api::ImageRendering::Auto,
api::AlphaType::PremultipliedAlpha,
blob_img2.as_image(),
ColorF::WHITE,
);
builder.pop_stacking_context();
}
}
fn main() {
let workers =
ThreadPoolBuilder::new().thread_name(|idx| format!("WebRender:Worker#{}", idx))
.build();
let workers = Arc::new(workers.unwrap());
let opts = webrender::RendererOptions {
workers: Some(Arc::clone(&workers)),
// Register our blob renderer, so that WebRender integrates it in the resource cache..
// Share the same pool of worker threads between WebRender and our blob renderer.
blob_image_handler: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))),
..Default::default()
};
let mut app = App {};
boilerplate::main_wrapper(&mut app, Some(opts));
}

View file

@ -1,329 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use gleam::gl;
use glutin;
use std::env;
use std::path::PathBuf;
use webrender;
use winit;
use webrender::{DebugFlags, ShaderPrecacheFlags};
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
struct Notifier {
events_proxy: winit::EventsLoopProxy,
}
impl Notifier {
fn new(events_proxy: winit::EventsLoopProxy) -> Notifier {
Notifier { events_proxy }
}
}
impl RenderNotifier for Notifier {
fn clone(&self) -> Box<dyn RenderNotifier> {
Box::new(Notifier {
events_proxy: self.events_proxy.clone(),
})
}
fn wake_up(&self, _composite_needed: bool) {
#[cfg(not(target_os = "android"))]
let _ = self.events_proxy.wakeup();
}
fn new_frame_ready(&self,
_: DocumentId,
_scrolled: bool,
composite_needed: bool,
_render_time: Option<u64>) {
self.wake_up(composite_needed);
}
}
pub trait HandyDandyRectBuilder {
fn to(&self, x2: i32, y2: i32) -> LayoutRect;
fn by(&self, w: i32, h: i32) -> LayoutRect;
}
// Allows doing `(x, y).to(x2, y2)` or `(x, y).by(width, height)` with i32
// values to build a f32 LayoutRect
impl HandyDandyRectBuilder for (i32, i32) {
fn to(&self, x2: i32, y2: i32) -> LayoutRect {
LayoutRect::new(
LayoutPoint::new(self.0 as f32, self.1 as f32),
LayoutSize::new((x2 - self.0) as f32, (y2 - self.1) as f32),
)
}
fn by(&self, w: i32, h: i32) -> LayoutRect {
LayoutRect::new(
LayoutPoint::new(self.0 as f32, self.1 as f32),
LayoutSize::new(w as f32, h as f32),
)
}
}
pub trait Example {
const TITLE: &'static str = "WebRender Sample App";
const PRECACHE_SHADER_FLAGS: ShaderPrecacheFlags = ShaderPrecacheFlags::EMPTY;
const WIDTH: u32 = 1920;
const HEIGHT: u32 = 1080;
fn render(
&mut self,
api: &mut RenderApi,
builder: &mut DisplayListBuilder,
txn: &mut Transaction,
device_size: DeviceIntSize,
pipeline_id: PipelineId,
document_id: DocumentId,
);
fn on_event(
&mut self,
_: winit::WindowEvent,
_: &mut RenderApi,
_: DocumentId,
) -> bool {
false
}
fn get_image_handler(
&mut self,
_gl: &dyn gl::Gl,
) -> Option<Box<dyn ExternalImageHandler>> {
None
}
fn draw_custom(&mut self, _gl: &dyn gl::Gl) {
}
}
pub fn main_wrapper<E: Example>(
example: &mut E,
options: Option<webrender::RendererOptions>,
) {
env_logger::init();
#[cfg(target_os = "macos")]
{
use core_foundation::{self as cf, base::TCFType};
let i = cf::bundle::CFBundle::main_bundle().info_dictionary();
let mut i = unsafe { i.to_mutable() };
i.set(
cf::string::CFString::new("NSSupportsAutomaticGraphicsSwitching"),
cf::boolean::CFBoolean::true_value().into_CFType(),
);
}
let args: Vec<String> = env::args().collect();
let res_path = if args.len() > 1 {
Some(PathBuf::from(&args[1]))
} else {
None
};
let mut events_loop = winit::EventsLoop::new();
let window_builder = winit::WindowBuilder::new()
.with_title(E::TITLE)
.with_multitouch()
.with_dimensions(winit::dpi::LogicalSize::new(E::WIDTH as f64, E::HEIGHT as f64));
let windowed_context = glutin::ContextBuilder::new()
.with_gl(glutin::GlRequest::GlThenGles {
opengl_version: (3, 2),
opengles_version: (3, 0),
})
.build_windowed(window_builder, &events_loop)
.unwrap();
let windowed_context = unsafe { windowed_context.make_current().unwrap() };
let gl = match windowed_context.get_api() {
glutin::Api::OpenGl => unsafe {
gl::GlFns::load_with(
|symbol| windowed_context.get_proc_address(symbol) as *const _
)
},
glutin::Api::OpenGlEs => unsafe {
gl::GlesFns::load_with(
|symbol| windowed_context.get_proc_address(symbol) as *const _
)
},
glutin::Api::WebGl => unimplemented!(),
};
println!("OpenGL version {}", gl.get_string(gl::VERSION));
println!("Shader resource path: {:?}", res_path);
let device_pixel_ratio = windowed_context.window().get_hidpi_factor() as f32;
println!("Device pixel ratio: {}", device_pixel_ratio);
println!("Loading shaders...");
let mut debug_flags = DebugFlags::ECHO_DRIVER_MESSAGES | DebugFlags::TEXTURE_CACHE_DBG;
let opts = webrender::RendererOptions {
resource_override_path: res_path,
precache_flags: E::PRECACHE_SHADER_FLAGS,
device_pixel_ratio,
clear_color: Some(ColorF::new(0.3, 0.0, 0.0, 1.0)),
debug_flags,
//allow_texture_swizzling: false,
..options.unwrap_or(webrender::RendererOptions::default())
};
let device_size = {
let size = windowed_context
.window()
.get_inner_size()
.unwrap()
.to_physical(device_pixel_ratio as f64);
DeviceIntSize::new(size.width as i32, size.height as i32)
};
let notifier = Box::new(Notifier::new(events_loop.create_proxy()));
let (mut renderer, sender) = webrender::Renderer::new(
gl.clone(),
notifier,
opts,
None,
).unwrap();
let mut api = sender.create_api();
let document_id = api.add_document(device_size);
let external = example.get_image_handler(&*gl);
if let Some(external_image_handler) = external {
renderer.set_external_image_handler(external_image_handler);
}
let epoch = Epoch(0);
let pipeline_id = PipelineId(0, 0);
let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio);
let mut builder = DisplayListBuilder::new(pipeline_id);
let mut txn = Transaction::new();
example.render(
&mut api,
&mut builder,
&mut txn,
device_size,
pipeline_id,
document_id,
);
txn.set_display_list(
epoch,
Some(ColorF::new(0.3, 0.0, 0.0, 1.0)),
layout_size,
builder.finalize(),
true,
);
txn.set_root_pipeline(pipeline_id);
txn.generate_frame(0);
api.send_transaction(document_id, txn);
println!("Entering event loop");
events_loop.run_forever(|global_event| {
let mut txn = Transaction::new();
let mut custom_event = true;
let old_flags = debug_flags;
let win_event = match global_event {
winit::Event::WindowEvent { event, .. } => event,
_ => return winit::ControlFlow::Continue,
};
match win_event {
winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break,
winit::WindowEvent::AxisMotion { .. } |
winit::WindowEvent::CursorMoved { .. } => {
custom_event = example.on_event(
win_event,
&mut api,
document_id,
);
// skip high-frequency events from triggering a frame draw.
if !custom_event {
return winit::ControlFlow::Continue;
}
},
winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Pressed,
virtual_keycode: Some(key),
..
},
..
} => match key {
winit::VirtualKeyCode::Escape => return winit::ControlFlow::Break,
winit::VirtualKeyCode::P => debug_flags.toggle(DebugFlags::PROFILER_DBG),
winit::VirtualKeyCode::O => debug_flags.toggle(DebugFlags::RENDER_TARGET_DBG),
winit::VirtualKeyCode::I => debug_flags.toggle(DebugFlags::TEXTURE_CACHE_DBG),
winit::VirtualKeyCode::T => debug_flags.toggle(DebugFlags::PICTURE_CACHING_DBG),
winit::VirtualKeyCode::Q => debug_flags.toggle(
DebugFlags::GPU_TIME_QUERIES | DebugFlags::GPU_SAMPLE_QUERIES
),
winit::VirtualKeyCode::G => debug_flags.toggle(DebugFlags::GPU_CACHE_DBG),
winit::VirtualKeyCode::Key1 => txn.set_document_view(
device_size.into(),
1.0
),
winit::VirtualKeyCode::Key2 => txn.set_document_view(
device_size.into(),
2.0
),
winit::VirtualKeyCode::M => api.notify_memory_pressure(),
winit::VirtualKeyCode::C => {
let path: PathBuf = "../captures/example".into();
//TODO: switch between SCENE/FRAME capture types
// based on "shift" modifier, when `glutin` is updated.
let bits = CaptureBits::all();
api.save_capture(path, bits);
},
_ => {
custom_event = example.on_event(
win_event,
&mut api,
document_id,
)
},
},
other => custom_event = example.on_event(
other,
&mut api,
document_id,
),
};
if debug_flags != old_flags {
api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
}
if custom_event {
let mut builder = DisplayListBuilder::new(pipeline_id);
example.render(
&mut api,
&mut builder,
&mut txn,
device_size,
pipeline_id,
document_id,
);
txn.set_display_list(
epoch,
Some(ColorF::new(0.3, 0.0, 0.0, 1.0)),
layout_size,
builder.finalize(),
true,
);
txn.generate_frame(0);
}
api.send_transaction(document_id, txn);
renderer.update();
renderer.render(device_size, 0).unwrap();
let _ = renderer.flush_pipeline_info();
example.draw_custom(&*gl);
windowed_context.swap_buffers().ok();
winit::ControlFlow::Continue
});
renderer.deinit();
}

View file

@ -1,19 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use webrender::api::{ImageData, ImageDescriptor, ImageFormat, ImageDescriptorFlags};
pub fn make_checkerboard(width: u32, height: u32) -> (ImageDescriptor, ImageData) {
let mut image_data = Vec::new();
for y in 0 .. height {
for x in 0 .. width {
let lum = 255 * (((x & 8) == 0) ^ ((y & 8) == 0)) as u8;
image_data.extend_from_slice(&[lum, lum, lum, 0xff]);
}
}
(
ImageDescriptor::new(width as i32, height as i32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::new(image_data)
)
}

View file

@ -1,143 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::Example;
use euclid::Scale;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
// This example creates multiple documents overlapping each other with
// specified layer indices.
struct Document {
id: DocumentId,
pipeline_id: PipelineId,
content_rect: LayoutRect,
color: ColorF,
}
struct App {
documents: Vec<Document>,
}
impl App {
fn init(
&mut self,
api: &mut RenderApi,
device_pixel_ratio: f32,
) {
let init_data = vec![
(
PipelineId(1, 0),
ColorF::new(0.0, 1.0, 0.0, 1.0),
DeviceIntPoint::new(0, 0),
),
(
PipelineId(2, 0),
ColorF::new(1.0, 1.0, 0.0, 1.0),
DeviceIntPoint::new(200, 0),
),
(
PipelineId(3, 0),
ColorF::new(1.0, 0.0, 0.0, 1.0),
DeviceIntPoint::new(200, 200),
),
(
PipelineId(4, 0),
ColorF::new(1.0, 0.0, 1.0, 1.0),
DeviceIntPoint::new(0, 200),
),
];
for (pipeline_id, color, offset) in init_data {
let size = DeviceIntSize::new(250, 250);
let bounds = DeviceIntRect::new(offset, size);
let document_id = api.add_document(size);
let mut txn = Transaction::new();
txn.set_document_view(bounds, device_pixel_ratio);
txn.set_root_pipeline(pipeline_id);
api.send_transaction(document_id, txn);
self.documents.push(Document {
id: document_id,
pipeline_id,
content_rect: LayoutRect::new(
LayoutPoint::origin(),
bounds.size.to_f32() / Scale::new(device_pixel_ratio),
),
color,
});
}
}
}
impl Example for App {
fn render(
&mut self,
api: &mut RenderApi,
_base_builder: &mut DisplayListBuilder,
_txn: &mut Transaction,
_device_size: DeviceIntSize,
_pipeline_id: PipelineId,
_: DocumentId,
) {
if self.documents.is_empty() {
// this is the first run, hack around the boilerplate,
// which assumes an example only needs one document
self.init(api, 1.0);
}
for doc in &self.documents {
let space_and_clip = SpaceAndClipInfo::root_scroll(doc.pipeline_id);
let mut builder = DisplayListBuilder::new(
doc.pipeline_id,
);
let local_rect = LayoutRect::new(
LayoutPoint::zero(),
doc.content_rect.size,
);
builder.push_simple_stacking_context(
doc.content_rect.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
builder.push_rect(
&CommonItemProperties::new(local_rect, space_and_clip),
local_rect,
doc.color,
);
builder.pop_stacking_context();
let mut txn = Transaction::new();
txn.set_display_list(
Epoch(0),
None,
doc.content_rect.size,
builder.finalize(),
true,
);
txn.generate_frame(0);
api.send_transaction(doc.id, txn);
}
}
}
fn main() {
let mut app = App {
documents: Vec::new(),
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -1,99 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
// This example uses the push_iframe API to nest a second pipeline's displaylist
// inside the root pipeline's display list. When it works, a green square is
// shown. If it fails, a red square is shown.
struct App {}
impl Example for App {
fn render(
&mut self,
api: &mut RenderApi,
builder: &mut DisplayListBuilder,
_txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
document_id: DocumentId,
) {
// All the sub_* things are for the nested pipeline
let sub_size = DeviceIntSize::new(100, 100);
let sub_bounds = (0, 0).to(sub_size.width as i32, sub_size.height as i32);
let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id);
let mut space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
sub_builder.push_simple_stacking_context(
sub_bounds.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
// green rect visible == success
sub_builder.push_rect(
&CommonItemProperties::new(sub_bounds, space_and_clip),
sub_bounds,
ColorF::new(0.0, 1.0, 0.0, 1.0)
);
sub_builder.pop_stacking_context();
let mut txn = Transaction::new();
txn.set_display_list(
Epoch(0),
None,
sub_bounds.size,
sub_builder.finalize(),
true,
);
api.send_transaction(document_id, txn);
space_and_clip.spatial_id = builder.push_reference_frame(
sub_bounds.origin,
space_and_clip.spatial_id,
TransformStyle::Flat,
PropertyBinding::Binding(PropertyBindingKey::new(42), LayoutTransform::identity()),
ReferenceFrameKind::Transform {
is_2d_scale_translation: false,
should_snap: false,
},
);
// And this is for the root pipeline
builder.push_simple_stacking_context(
sub_bounds.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
// red rect under the iframe: if this is visible, things have gone wrong
builder.push_rect(
&CommonItemProperties::new(sub_bounds, space_and_clip),
sub_bounds,
ColorF::new(1.0, 0.0, 0.0, 1.0)
);
builder.push_iframe(sub_bounds, sub_bounds, &space_and_clip, sub_pipeline_id, false);
builder.pop_stacking_context();
builder.pop_reference_frame();
}
}
fn main() {
let mut app = App {};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -1,122 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
#[path = "common/image_helper.rs"]
mod image_helper;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
struct App {
image_key: ImageKey,
}
impl Example for App {
fn render(
&mut self,
_api: &mut RenderApi,
builder: &mut DisplayListBuilder,
txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let (image_descriptor, image_data) = image_helper::make_checkerboard(32, 32);
txn.add_image(
self.image_key,
image_descriptor,
image_data,
None,
);
let bounds = (0, 0).to(512, 512);
let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
builder.push_simple_stacking_context(
bounds.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
let image_size = LayoutSize::new(100.0, 100.0);
builder.push_image(
&CommonItemProperties::new(
LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
space_and_clip,
),
bounds,
ImageRendering::Auto,
AlphaType::PremultipliedAlpha,
self.image_key,
ColorF::WHITE,
);
builder.push_image(
&CommonItemProperties::new(
LayoutRect::new(LayoutPoint::new(250.0, 100.0), image_size),
space_and_clip,
),
bounds,
ImageRendering::Pixelated,
AlphaType::PremultipliedAlpha,
self.image_key,
ColorF::WHITE,
);
builder.pop_stacking_context();
}
fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool {
match event {
winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Pressed,
virtual_keycode: Some(winit::VirtualKeyCode::Space),
..
},
..
} => {
let mut image_data = Vec::new();
for y in 0 .. 64 {
for x in 0 .. 64 {
let r = 255 * ((y & 32) == 0) as u8;
let g = 255 * ((x & 32) == 0) as u8;
image_data.extend_from_slice(&[0, g, r, 0xff]);
}
}
let mut txn = Transaction::new();
txn.update_image(
self.image_key,
ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::new(image_data),
&DirtyRect::All,
);
let mut txn = Transaction::new();
txn.generate_frame(0);
api.send_transaction(document_id, txn);
}
_ => {}
}
false
}
}
fn main() {
let mut app = App {
image_key: ImageKey(IdNamespace(0), 0),
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -1,327 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
use gleam::gl;
use glutin::NotCurrent;
use std::fs::File;
use std::io::Read;
use webrender::api::*;
use webrender::api::units::*;
use webrender::render_api::*;
use webrender::DebugFlags;
use winit::dpi::LogicalSize;
struct Notifier {
events_proxy: winit::EventsLoopProxy,
}
impl Notifier {
fn new(events_proxy: winit::EventsLoopProxy) -> Notifier {
Notifier { events_proxy }
}
}
impl RenderNotifier for Notifier {
fn clone(&self) -> Box<dyn RenderNotifier> {
Box::new(Notifier {
events_proxy: self.events_proxy.clone(),
})
}
fn wake_up(&self, _composite_needed: bool) {
#[cfg(not(target_os = "android"))]
let _ = self.events_proxy.wakeup();
}
fn new_frame_ready(&self,
_: DocumentId,
_scrolled: bool,
composite_needed: bool,
_render_time: Option<u64>) {
self.wake_up(composite_needed);
}
}
struct Window {
events_loop: winit::EventsLoop, //TODO: share events loop?
context: Option<glutin::WindowedContext<NotCurrent>>,
renderer: webrender::Renderer,
name: &'static str,
pipeline_id: PipelineId,
document_id: DocumentId,
epoch: Epoch,
api: RenderApi,
font_instance_key: FontInstanceKey,
}
impl Window {
fn new(name: &'static str, clear_color: ColorF) -> Self {
let events_loop = winit::EventsLoop::new();
let window_builder = winit::WindowBuilder::new()
.with_title(name)
.with_multitouch()
.with_dimensions(LogicalSize::new(800., 600.));
let context = glutin::ContextBuilder::new()
.with_gl(glutin::GlRequest::GlThenGles {
opengl_version: (3, 2),
opengles_version: (3, 0),
})
.build_windowed(window_builder, &events_loop)
.unwrap();
let context = unsafe { context.make_current().unwrap() };
let gl = match context.get_api() {
glutin::Api::OpenGl => unsafe {
gl::GlFns::load_with(|symbol| context.get_proc_address(symbol) as *const _)
},
glutin::Api::OpenGlEs => unsafe {
gl::GlesFns::load_with(|symbol| context.get_proc_address(symbol) as *const _)
},
glutin::Api::WebGl => unimplemented!(),
};
let device_pixel_ratio = context.window().get_hidpi_factor() as f32;
let opts = webrender::RendererOptions {
device_pixel_ratio,
clear_color: Some(clear_color),
..webrender::RendererOptions::default()
};
let device_size = {
let size = context
.window()
.get_inner_size()
.unwrap()
.to_physical(device_pixel_ratio as f64);
DeviceIntSize::new(size.width as i32, size.height as i32)
};
let notifier = Box::new(Notifier::new(events_loop.create_proxy()));
let (renderer, sender) = webrender::Renderer::new(gl.clone(), notifier, opts, None).unwrap();
let mut api = sender.create_api();
let document_id = api.add_document(device_size);
let epoch = Epoch(0);
let pipeline_id = PipelineId(0, 0);
let mut txn = Transaction::new();
let font_key = api.generate_font_key();
let font_bytes = load_file("../wrench/reftests/text/FreeSans.ttf");
txn.add_raw_font(font_key, font_bytes, 0);
let font_instance_key = api.generate_font_instance_key();
txn.add_font_instance(font_instance_key, font_key, 32.0, None, None, Vec::new());
api.send_transaction(document_id, txn);
Window {
events_loop,
context: Some(unsafe { context.make_not_current().unwrap() }),
renderer,
name,
epoch,
pipeline_id,
document_id,
api,
font_instance_key,
}
}
fn tick(&mut self) -> bool {
let mut do_exit = false;
let my_name = &self.name;
let renderer = &mut self.renderer;
let api = &mut self.api;
self.events_loop.poll_events(|global_event| match global_event {
winit::Event::WindowEvent { event, .. } => match event {
winit::WindowEvent::CloseRequested |
winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
virtual_keycode: Some(winit::VirtualKeyCode::Escape),
..
},
..
} => {
do_exit = true
}
winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Pressed,
virtual_keycode: Some(winit::VirtualKeyCode::P),
..
},
..
} => {
println!("set flags {}", my_name);
api.send_debug_cmd(DebugCommand::SetFlags(DebugFlags::PROFILER_DBG))
}
_ => {}
}
_ => {}
});
if do_exit {
return true
}
let context = unsafe { self.context.take().unwrap().make_current().unwrap() };
let device_pixel_ratio = context.window().get_hidpi_factor() as f32;
let device_size = {
let size = context
.window()
.get_inner_size()
.unwrap()
.to_physical(device_pixel_ratio as f64);
DeviceIntSize::new(size.width as i32, size.height as i32)
};
let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio);
let mut txn = Transaction::new();
let mut builder = DisplayListBuilder::new(self.pipeline_id);
let space_and_clip = SpaceAndClipInfo::root_scroll(self.pipeline_id);
let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
builder.push_simple_stacking_context(
bounds.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
builder.push_rect(
&CommonItemProperties::new(
LayoutRect::new(
LayoutPoint::new(100.0, 200.0),
LayoutSize::new(100.0, 200.0),
),
space_and_clip,
),
LayoutRect::new(
LayoutPoint::new(100.0, 200.0),
LayoutSize::new(100.0, 200.0),
),
ColorF::new(0.0, 1.0, 0.0, 1.0));
let text_bounds = LayoutRect::new(
LayoutPoint::new(100.0, 50.0),
LayoutSize::new(700.0, 200.0)
);
let glyphs = vec![
GlyphInstance {
index: 48,
point: LayoutPoint::new(100.0, 100.0),
},
GlyphInstance {
index: 68,
point: LayoutPoint::new(150.0, 100.0),
},
GlyphInstance {
index: 80,
point: LayoutPoint::new(200.0, 100.0),
},
GlyphInstance {
index: 82,
point: LayoutPoint::new(250.0, 100.0),
},
GlyphInstance {
index: 81,
point: LayoutPoint::new(300.0, 100.0),
},
GlyphInstance {
index: 3,
point: LayoutPoint::new(350.0, 100.0),
},
GlyphInstance {
index: 86,
point: LayoutPoint::new(400.0, 100.0),
},
GlyphInstance {
index: 79,
point: LayoutPoint::new(450.0, 100.0),
},
GlyphInstance {
index: 72,
point: LayoutPoint::new(500.0, 100.0),
},
GlyphInstance {
index: 83,
point: LayoutPoint::new(550.0, 100.0),
},
GlyphInstance {
index: 87,
point: LayoutPoint::new(600.0, 100.0),
},
GlyphInstance {
index: 17,
point: LayoutPoint::new(650.0, 100.0),
},
];
builder.push_text(
&CommonItemProperties::new(
text_bounds,
space_and_clip,
),
text_bounds,
&glyphs,
self.font_instance_key,
ColorF::new(1.0, 1.0, 0.0, 1.0),
None,
);
builder.pop_stacking_context();
txn.set_display_list(
self.epoch,
None,
layout_size,
builder.finalize(),
true,
);
txn.set_root_pipeline(self.pipeline_id);
txn.generate_frame(0);
api.send_transaction(self.document_id, txn);
renderer.update();
renderer.render(device_size, 0).unwrap();
context.swap_buffers().ok();
self.context = Some(unsafe { context.make_not_current().unwrap() });
false
}
fn deinit(self) {
self.renderer.deinit();
}
}
fn main() {
let mut win1 = Window::new("window1", ColorF::new(0.3, 0.0, 0.0, 1.0));
let mut win2 = Window::new("window2", ColorF::new(0.0, 0.3, 0.0, 1.0));
loop {
if win1.tick() {
break;
}
if win2.tick() {
break;
}
}
win1.deinit();
win2.deinit();
}
fn load_file(name: &str) -> Vec<u8> {
let mut file = File::open(name).unwrap();
let mut buffer = vec![];
file.read_to_end(&mut buffer).unwrap();
buffer
}

View file

@ -1,243 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use euclid::SideOffsets2D;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
use winit::dpi::LogicalPosition;
const EXT_SCROLL_ID_ROOT: u64 = 1;
const EXT_SCROLL_ID_CONTENT: u64 = 2;
struct App {
cursor_position: WorldPoint,
scroll_origin: LayoutPoint,
}
impl Example for App {
fn render(
&mut self,
_api: &mut RenderApi,
builder: &mut DisplayListBuilder,
_txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
builder.push_simple_stacking_context(
LayoutPoint::zero(),
root_space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
if true {
// scrolling and clips stuff
// let's make a scrollbox
let scrollbox = (0, 0).to(300, 400);
builder.push_simple_stacking_context(
LayoutPoint::new(10., 10.),
root_space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
// set the scrolling clip
let space_and_clip1 = builder.define_scroll_frame(
&root_space_and_clip,
ExternalScrollId(EXT_SCROLL_ID_ROOT, PipelineId::dummy()),
(0, 0).by(1000, 1000),
scrollbox,
ScrollSensitivity::ScriptAndInputEvents,
LayoutVector2D::zero(),
);
// now put some content into it.
// start with a white background
let info = CommonItemProperties::new((0, 0).to(1000, 1000), space_and_clip1);
builder.push_hit_test(&info, (0, 1));
builder.push_rect(&info, info.clip_rect, ColorF::new(1.0, 1.0, 1.0, 1.0));
// let's make a 50x50 blue square as a visual reference
let info = CommonItemProperties::new((0, 0).to(50, 50), space_and_clip1);
builder.push_hit_test(&info, (0, 2));
builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 0.0, 1.0, 1.0));
// and a 50x50 green square next to it with an offset clip
// to see what that looks like
let info = CommonItemProperties::new(
(50, 0).to(100, 50).intersection(&(60, 10).to(110, 60)).unwrap(),
space_and_clip1,
);
builder.push_hit_test(&info, (0, 3));
builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 0.0, 1.0));
// Below the above rectangles, set up a nested scrollbox. It's still in
// the same stacking context, so note that the rects passed in need to
// be relative to the stacking context.
let space_and_clip2 = builder.define_scroll_frame(
&space_and_clip1,
ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
(0, 100).to(300, 1000),
(0, 100).to(200, 300),
ScrollSensitivity::ScriptAndInputEvents,
LayoutVector2D::zero(),
);
// give it a giant gray background just to distinguish it and to easily
// visually identify the nested scrollbox
let info = CommonItemProperties::new(
(-1000, -1000).to(5000, 5000),
space_and_clip2,
);
builder.push_hit_test(&info, (0, 4));
builder.push_rect(&info, info.clip_rect, ColorF::new(0.5, 0.5, 0.5, 1.0));
// add a teal square to visualize the scrolling/clipping behaviour
// as you scroll the nested scrollbox
let info = CommonItemProperties::new((0, 200).to(50, 250), space_and_clip2);
builder.push_hit_test(&info, (0, 5));
builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
// Add a sticky frame. It will "stick" twice while scrolling, once
// at a margin of 10px from the bottom, for 40 pixels of scrolling,
// and once at a margin of 10px from the top, for 60 pixels of
// scrolling.
let sticky_id = builder.define_sticky_frame(
space_and_clip2.spatial_id,
(50, 350).by(50, 50),
SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
StickyOffsetBounds::new(-40.0, 60.0),
StickyOffsetBounds::new(0.0, 0.0),
LayoutVector2D::new(0.0, 0.0)
);
let info = CommonItemProperties::new(
(50, 350).by(50, 50),
SpaceAndClipInfo {
spatial_id: sticky_id,
clip_id: space_and_clip2.clip_id,
},
);
builder.push_hit_test(&info, (0, 6));
builder.push_rect(
&info,
info.clip_rect,
ColorF::new(0.5, 0.5, 1.0, 1.0),
);
// just for good measure add another teal square further down and to
// the right, which can be scrolled into view by the user
let info = CommonItemProperties::new(
(250, 350).to(300, 400),
space_and_clip2,
);
builder.push_hit_test(&info, (0, 7));
builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0));
builder.pop_stacking_context();
}
builder.pop_stacking_context();
}
fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool {
let mut txn = Transaction::new();
match event {
winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Pressed,
virtual_keycode: Some(key),
..
},
..
} => {
let offset = match key {
winit::VirtualKeyCode::Down => Some(LayoutVector2D::new(0.0, -10.0)),
winit::VirtualKeyCode::Up => Some(LayoutVector2D::new(0.0, 10.0)),
winit::VirtualKeyCode::Right => Some(LayoutVector2D::new(-10.0, 0.0)),
winit::VirtualKeyCode::Left => Some(LayoutVector2D::new(10.0, 0.0)),
_ => None,
};
let zoom = match key {
winit::VirtualKeyCode::Key0 => Some(1.0),
winit::VirtualKeyCode::Minus => Some(0.8),
winit::VirtualKeyCode::Equals => Some(1.25),
_ => None,
};
if let Some(offset) = offset {
self.scroll_origin += offset;
txn.scroll_node_with_id(
self.scroll_origin,
ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
ScrollClamping::ToContentBounds,
);
txn.generate_frame(0);
}
if let Some(zoom) = zoom {
txn.set_pinch_zoom(ZoomFactor::new(zoom));
txn.generate_frame(0);
}
}
winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => {
self.cursor_position = WorldPoint::new(x as f32, y as f32);
}
winit::WindowEvent::MouseWheel { delta, .. } => {
const LINE_HEIGHT: f32 = 38.0;
let (dx, dy) = match delta {
winit::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
winit::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
};
self.scroll_origin += LayoutVector2D::new(dx, dy);
txn.scroll_node_with_id(
self.scroll_origin,
ExternalScrollId(EXT_SCROLL_ID_CONTENT, PipelineId::dummy()),
ScrollClamping::ToContentBounds,
);
txn.generate_frame(0);
}
winit::WindowEvent::MouseInput { .. } => {
let results = api.hit_test(
document_id,
None,
self.cursor_position,
);
println!("Hit test results:");
for item in &results.items {
println!("{:?}", item);
}
println!("");
}
_ => (),
}
api.send_transaction(document_id, txn);
false
}
}
fn main() {
let mut app = App {
cursor_position: WorldPoint::zero(),
scroll_origin: LayoutPoint::zero(),
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -1,322 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::{Example, HandyDandyRectBuilder};
use gleam::gl;
use std::mem;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
struct ImageGenerator {
patterns: [[u8; 3]; 6],
next_pattern: usize,
current_image: Vec<u8>,
}
impl ImageGenerator {
fn new() -> Self {
ImageGenerator {
next_pattern: 0,
patterns: [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
[1, 1, 0],
[0, 1, 1],
[1, 0, 1],
],
current_image: Vec::new(),
}
}
fn generate_image(&mut self, size: i32) {
let pattern = &self.patterns[self.next_pattern];
self.current_image.clear();
for y in 0 .. size {
for x in 0 .. size {
let lum = 255 * (1 - (((x & 8) == 0) ^ ((y & 8) == 0)) as u8);
self.current_image.extend_from_slice(&[
lum * pattern[0],
lum * pattern[1],
lum * pattern[2],
0xff,
]);
}
}
self.next_pattern = (self.next_pattern + 1) % self.patterns.len();
}
fn take(&mut self) -> Vec<u8> {
mem::replace(&mut self.current_image, Vec::new())
}
}
impl ExternalImageHandler for ImageGenerator {
fn lock(
&mut self,
_key: ExternalImageId,
channel_index: u8,
_rendering: ImageRendering
) -> ExternalImage {
self.generate_image(channel_index as i32);
ExternalImage {
uv: TexelRect::new(0.0, 0.0, 1.0, 1.0),
source: ExternalImageSource::RawData(&self.current_image),
}
}
fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {}
}
struct App {
stress_keys: Vec<ImageKey>,
image_key: Option<ImageKey>,
image_generator: ImageGenerator,
swap_keys: Vec<ImageKey>,
swap_index: usize,
}
impl Example for App {
fn render(
&mut self,
api: &mut RenderApi,
builder: &mut DisplayListBuilder,
txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let bounds = (0, 0).to(512, 512);
let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
builder.push_simple_stacking_context(
bounds.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
let x0 = 50.0;
let y0 = 50.0;
let image_size = LayoutSize::new(4.0, 4.0);
if self.swap_keys.is_empty() {
let key0 = api.generate_image_key();
let key1 = api.generate_image_key();
self.image_generator.generate_image(128);
txn.add_image(
key0,
ImageDescriptor::new(128, 128, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::new(self.image_generator.take()),
None,
);
self.image_generator.generate_image(128);
txn.add_image(
key1,
ImageDescriptor::new(128, 128, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::new(self.image_generator.take()),
None,
);
self.swap_keys.push(key0);
self.swap_keys.push(key1);
}
for (i, key) in self.stress_keys.iter().enumerate() {
let x = (i % 128) as f32;
let y = (i / 128) as f32;
let info = CommonItemProperties::new(
LayoutRect::new(
LayoutPoint::new(x0 + image_size.width * x, y0 + image_size.height * y),
image_size,
),
space_and_clip,
);
builder.push_image(
&info,
bounds,
ImageRendering::Auto,
AlphaType::PremultipliedAlpha,
*key,
ColorF::WHITE,
);
}
if let Some(image_key) = self.image_key {
let image_size = LayoutSize::new(100.0, 100.0);
let info = CommonItemProperties::new(
LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
space_and_clip,
);
builder.push_image(
&info,
bounds,
ImageRendering::Auto,
AlphaType::PremultipliedAlpha,
image_key,
ColorF::WHITE,
);
}
let swap_key = self.swap_keys[self.swap_index];
let image_size = LayoutSize::new(64.0, 64.0);
let info = CommonItemProperties::new(
LayoutRect::new(LayoutPoint::new(100.0, 400.0), image_size),
space_and_clip,
);
builder.push_image(
&info,
bounds,
ImageRendering::Auto,
AlphaType::PremultipliedAlpha,
swap_key,
ColorF::WHITE,
);
self.swap_index = 1 - self.swap_index;
builder.pop_stacking_context();
}
fn on_event(
&mut self,
event: winit::WindowEvent,
api: &mut RenderApi,
document_id: DocumentId,
) -> bool {
match event {
winit::WindowEvent::KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Pressed,
virtual_keycode: Some(key),
..
},
..
} => {
let mut txn = Transaction::new();
match key {
winit::VirtualKeyCode::S => {
self.stress_keys.clear();
for _ in 0 .. 16 {
for _ in 0 .. 16 {
let size = 4;
let image_key = api.generate_image_key();
self.image_generator.generate_image(size);
txn.add_image(
image_key,
ImageDescriptor::new(
size,
size,
ImageFormat::BGRA8,
ImageDescriptorFlags::IS_OPAQUE,
),
ImageData::new(self.image_generator.take()),
None,
);
self.stress_keys.push(image_key);
}
}
}
winit::VirtualKeyCode::D => if let Some(image_key) = self.image_key.take() {
txn.delete_image(image_key);
},
winit::VirtualKeyCode::U => if let Some(image_key) = self.image_key {
let size = 128;
self.image_generator.generate_image(size);
txn.update_image(
image_key,
ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::new(self.image_generator.take()),
&DirtyRect::All,
);
},
winit::VirtualKeyCode::E => {
if let Some(image_key) = self.image_key.take() {
txn.delete_image(image_key);
}
let size = 32;
let image_key = api.generate_image_key();
let image_data = ExternalImageData {
id: ExternalImageId(0),
channel_index: size as u8,
image_type: ExternalImageType::Buffer,
};
txn.add_image(
image_key,
ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::External(image_data),
None,
);
self.image_key = Some(image_key);
}
winit::VirtualKeyCode::R => {
if let Some(image_key) = self.image_key.take() {
txn.delete_image(image_key);
}
let image_key = api.generate_image_key();
let size = 32;
self.image_generator.generate_image(size);
txn.add_image(
image_key,
ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::new(self.image_generator.take()),
None,
);
self.image_key = Some(image_key);
}
_ => {}
}
api.send_transaction(document_id, txn);
return true;
}
_ => {}
}
false
}
fn get_image_handler(
&mut self,
_gl: &dyn gl::Gl,
) -> Option<Box<dyn ExternalImageHandler>> {
Some(Box::new(ImageGenerator::new()))
}
}
fn main() {
let mut app = App {
image_key: None,
stress_keys: Vec::new(),
image_generator: ImageGenerator::new(),
swap_keys: Vec::new(),
swap_index: 0,
};
boilerplate::main_wrapper(&mut app, None);
}

View file

@ -1,225 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate gleam;
extern crate glutin;
extern crate webrender;
extern crate winit;
#[path = "common/boilerplate.rs"]
mod boilerplate;
use crate::boilerplate::Example;
use gleam::gl;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
fn init_gl_texture(
id: gl::GLuint,
internal: gl::GLenum,
external: gl::GLenum,
bytes: &[u8],
gl: &dyn gl::Gl,
) {
gl.bind_texture(gl::TEXTURE_2D, id);
gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as gl::GLint);
gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint);
gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint);
gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint);
gl.tex_image_2d(
gl::TEXTURE_2D,
0,
internal as gl::GLint,
100,
100,
0,
external,
gl::UNSIGNED_BYTE,
Some(bytes),
);
gl.bind_texture(gl::TEXTURE_2D, 0);
}
struct YuvImageProvider {
texture_ids: Vec<gl::GLuint>,
}
impl YuvImageProvider {
fn new(gl: &dyn gl::Gl) -> Self {
let texture_ids = gl.gen_textures(4);
init_gl_texture(texture_ids[0], gl::RED, gl::RED, &[127; 100 * 100], gl);
init_gl_texture(texture_ids[1], gl::RG8, gl::RG, &[0; 100 * 100 * 2], gl);
init_gl_texture(texture_ids[2], gl::RED, gl::RED, &[127; 100 * 100], gl);
init_gl_texture(texture_ids[3], gl::RED, gl::RED, &[127; 100 * 100], gl);
YuvImageProvider {
texture_ids
}
}
}
impl ExternalImageHandler for YuvImageProvider {
fn lock(
&mut self,
key: ExternalImageId,
_channel_index: u8,
_rendering: ImageRendering
) -> ExternalImage {
let id = self.texture_ids[key.0 as usize];
ExternalImage {
uv: TexelRect::new(0.0, 0.0, 1.0, 1.0),
source: ExternalImageSource::NativeTexture(id),
}
}
fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {
}
}
struct App {
texture_id: gl::GLuint,
current_value: u8,
}
impl Example for App {
fn render(
&mut self,
api: &mut RenderApi,
builder: &mut DisplayListBuilder,
txn: &mut Transaction,
_device_size: DeviceIntSize,
pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(500.0, 500.0));
let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
builder.push_simple_stacking_context(
bounds.origin,
space_and_clip.spatial_id,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
);
let yuv_chanel1 = api.generate_image_key();
let yuv_chanel2 = api.generate_image_key();
let yuv_chanel2_1 = api.generate_image_key();
let yuv_chanel3 = api.generate_image_key();
txn.add_image(
yuv_chanel1,
ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::External(ExternalImageData {
id: ExternalImageId(0),
channel_index: 0,
image_type: ExternalImageType::TextureHandle(
ImageBufferKind::Texture2D,
),
}),
None,
);
txn.add_image(
yuv_chanel2,
ImageDescriptor::new(100, 100, ImageFormat::RG8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::External(ExternalImageData {
id: ExternalImageId(1),
channel_index: 0,
image_type: ExternalImageType::TextureHandle(
ImageBufferKind::Texture2D,
),
}),
None,
);
txn.add_image(
yuv_chanel2_1,
ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::External(ExternalImageData {
id: ExternalImageId(2),
channel_index: 0,
image_type: ExternalImageType::TextureHandle(
ImageBufferKind::Texture2D,
),
}),
None,
);
txn.add_image(
yuv_chanel3,
ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE),
ImageData::External(ExternalImageData {
id: ExternalImageId(3),
channel_index: 0,
image_type: ExternalImageType::TextureHandle(
ImageBufferKind::Texture2D,
),
}),
None,
);
let info = CommonItemProperties::new(
LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
space_and_clip,
);
builder.push_yuv_image(
&info,
bounds,
YuvData::NV12(yuv_chanel1, yuv_chanel2),
ColorDepth::Color8,
YuvColorSpace::Rec601,
ColorRange::Limited,
ImageRendering::Auto,
);
let info = CommonItemProperties::new(
LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)),
space_and_clip,
);
builder.push_yuv_image(
&info,
bounds,
YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
ColorDepth::Color8,
YuvColorSpace::Rec601,
ColorRange::Limited,
ImageRendering::Auto,
);
builder.pop_stacking_context();
}
fn on_event(
&mut self,
_event: winit::WindowEvent,
_api: &mut RenderApi,
_document_id: DocumentId,
) -> bool {
false
}
fn get_image_handler(
&mut self,
gl: &dyn gl::Gl,
) -> Option<Box<dyn ExternalImageHandler>> {
let provider = YuvImageProvider::new(gl);
self.texture_id = provider.texture_ids[0];
Some(Box::new(provider))
}
fn draw_custom(&mut self, gl: &dyn gl::Gl) {
init_gl_texture(self.texture_id, gl::RED, gl::RED, &[self.current_value; 100 * 100], gl);
self.current_value = self.current_value.wrapping_add(1);
}
}
fn main() {
let mut app = App {
texture_id: 0,
current_value: 0,
};
let opts = webrender::RendererOptions {
..Default::default()
};
boilerplate::main_wrapper(&mut app, Some(opts));
}

View file

@ -1,11 +0,0 @@
[package]
name = "glsl-to-cxx"
version = "0.1.0"
license = "MPL-2.0"
authors = ["The Mozilla Project Developers", "Dimitri Sabadie"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
glsl = "4.0"

View file

@ -1,21 +0,0 @@
A GLSL to C++ translator.
Translates GLSL to vectorized C++. Intended for use with WebRender software backend.
Architecture
------------
GLSL code is parsed by the glsl crate. In hir.rs we traverse the resulting AST
and build a higher level representation by doing type checking and name
resolution. The resulting hir tree is traversed by lib.rs to output C++ code.
The generated C++ code is 4x wider then the original glsl. i.e. a glsl 'float'
becomes a C++ 'Float' which is represented by a xmm register (a vector of 4 floats).
Likewise, a vec4 becomes a struct of 4 'Float's for a total of 4 xmm registers and
16 floating point values.
Vector branching is flattened to non-branching code that unconditionally runs both
sides of the branch and combines the results with a mask based on the condition.
The compiler also supports scalarization. Values that are known to be the same
across all vector lanes are translated to scalars instead of vectors. Branches on
scalars are translated as actual branches.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,8 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use glsl_to_cxx::translate;
fn main() {
println!("{}", translate(&mut std::env::args()));
}

View file

@ -1,250 +0,0 @@
diff --git a/webrender/src/hit_test.rs b/webrender/src/hit_test.rs
index 4a73e2158d..e095d8db0c 100644
--- a/webrender/src/hit_test.rs
+++ b/webrender/src/hit_test.rs
@@ -2,13 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-use api::{BorderRadius, ClipMode, HitTestItem, HitTestResult, ItemTag, PrimitiveFlags};
+use api::{BorderRadius, ClipMode, HitTestItem, HitTestResult, ItemTag, PrimitiveFlags, HitTestFlags};
use api::{PipelineId, ApiHitTester, ClipId};
use api::units::*;
use crate::clip::{ClipItemKind, ClipStore, ClipNode, rounded_rectangle_contains_point};
use crate::clip::{polygon_contains_point};
use crate::prim_store::PolygonKey;
use crate::scene_builder_thread::Interners;
+use crate::spatial_node::SpatialNodeType;
use crate::spatial_tree::{SpatialNodeIndex, SpatialTree};
use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo};
use std::ops;
@@ -45,8 +46,9 @@ impl ApiHitTester for SharedHitTester {
fn hit_test(&self,
pipeline_id: Option<PipelineId>,
point: WorldPoint,
+ flags: HitTestFlags,
) -> HitTestResult {
- self.get_ref().hit_test(HitTest::new(pipeline_id, point))
+ self.get_ref().hit_test(HitTest::new(pipeline_id, point, flags))
}
}
@@ -355,6 +357,7 @@ impl HitTester {
self.spatial_nodes.clear();
self.spatial_nodes.reserve(spatial_tree.spatial_nodes.len());
+ self.pipeline_root_nodes.clear();
for (index, node) in spatial_tree.spatial_nodes.iter().enumerate() {
let index = SpatialNodeIndex::new(index);
@@ -380,6 +383,8 @@ impl HitTester {
}
pub fn hit_test(&self, test: HitTest) -> HitTestResult {
+ let point = test.get_absolute_point(self);
+
let mut result = HitTestResult::default();
let mut current_spatial_node_index = SpatialNodeIndex::INVALID;
@@ -402,7 +407,7 @@ impl HitTester {
point_in_layer = scroll_node
.world_content_transform
.inverse()
- .and_then(|inverted| inverted.transform_point2d(test.point));
+ .and_then(|inverted| inverted.transform_point2d(point));
current_spatial_node_index = item.spatial_node_index;
}
@@ -426,7 +431,7 @@ impl HitTester {
.world_content_transform;
let transformed_point = match transform
.inverse()
- .and_then(|inverted| inverted.transform_point2d(test.point))
+ .and_then(|inverted| inverted.transform_point2d(point))
{
Some(point) => point,
None => {
@@ -457,7 +462,7 @@ impl HitTester {
point_in_viewport = root_node
.world_viewport_transform
.inverse()
- .and_then(|inverted| inverted.transform_point2d(test.point))
+ .and_then(|inverted| inverted.transform_point2d(point))
.map(|pt| pt - scroll_node.external_scroll_offset);
current_root_spatial_node_index = root_spatial_node_index;
@@ -470,6 +475,10 @@ impl HitTester {
point_in_viewport,
point_relative_to_item: point_in_layer - item.rect.origin.to_vector(),
});
+
+ if !test.flags.contains(HitTestFlags::FIND_ALL) {
+ return result;
+ }
}
}
}
@@ -477,24 +486,51 @@ impl HitTester {
result.items.dedup();
result
}
+
+ fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestSpatialNode {
+ &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0 as usize]
+ }
+
}
#[derive(MallocSizeOf)]
pub struct HitTest {
pipeline_id: Option<PipelineId>,
point: WorldPoint,
+ flags: HitTestFlags,
}
impl HitTest {
pub fn new(
pipeline_id: Option<PipelineId>,
point: WorldPoint,
+ flags: HitTestFlags,
) -> HitTest {
HitTest {
pipeline_id,
point,
+ flags
}
}
+
+ fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
+ if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
+ return self.point;
+ }
+
+ let point = LayoutPoint::new(self.point.x, self.point.y);
+ self.pipeline_id
+ .and_then(|id|
+ hit_tester
+ .get_pipeline_root(id)
+ .world_viewport_transform
+ .transform_point2d(point)
+ )
+ .unwrap_or_else(|| {
+ WorldPoint::new(self.point.x, self.point.y)
+ })
+ }
+
}
/// Collect clips for a given ClipId, convert and add them to the hit testing
diff --git a/webrender/src/render_api.rs b/webrender/src/render_api.rs
index b84fa2c63a..b2c8a64e88 100644
--- a/webrender/src/render_api.rs
+++ b/webrender/src/render_api.rs
@@ -10,6 +10,7 @@ use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::Arc;
use std::u32;
+use api::HitTestFlags;
use time::precise_time_ns;
//use crate::api::peek_poke::PeekPoke;
use crate::api::channel::{Sender, single_msg_channel, unbounded_channel};
@@ -812,7 +813,7 @@ pub enum FrameMsg {
///
UpdateEpoch(PipelineId, Epoch),
///
- HitTest(Option<PipelineId>, WorldPoint, Sender<HitTestResult>),
+ HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, Sender<HitTestResult>),
///
RequestHitTester(Sender<Arc<dyn ApiHitTester>>),
///
@@ -1297,12 +1298,13 @@ impl RenderApi {
document_id: DocumentId,
pipeline_id: Option<PipelineId>,
point: WorldPoint,
+ flags: HitTestFlags,
) -> HitTestResult {
let (tx, rx) = single_msg_channel();
self.send_frame_msg(
document_id,
- FrameMsg::HitTest(pipeline_id, point, tx)
+ FrameMsg::HitTest(pipeline_id, point, flags, tx)
);
rx.recv().unwrap()
}
diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs
index 96bc600484..825e981b5c 100644
--- a/webrender/src/render_backend.rs
+++ b/webrender/src/render_backend.rs
@@ -535,14 +535,14 @@ impl Document {
FrameMsg::UpdateEpoch(pipeline_id, epoch) => {
self.scene.pipeline_epochs.insert(pipeline_id, epoch);
}
- FrameMsg::HitTest(pipeline_id, point, tx) => {
+ FrameMsg::HitTest(pipeline_id, point, flags, tx) => {
if !self.hit_tester_is_valid {
self.rebuild_hit_tester();
}
let result = match self.hit_tester {
Some(ref hit_tester) => {
- hit_tester.hit_test(HitTest::new(pipeline_id, point))
+ hit_tester.hit_test(HitTest::new(pipeline_id, point, flags))
}
None => HitTestResult { items: Vec::new() },
};
diff --git a/webrender_api/src/lib.rs b/webrender_api/src/lib.rs
index 7dc887ade9..b0446c1d0b 100644
--- a/webrender_api/src/lib.rs
+++ b/webrender_api/src/lib.rs
@@ -278,7 +278,7 @@ pub trait ApiHitTester: Send + Sync {
/// hit results so that only items inside that pipeline are matched. The vector
/// of hit results will contain all display items that match, ordered from
/// front to back.
- fn hit_test(&self, pipeline_id: Option<PipelineId>, point: WorldPoint) -> HitTestResult;
+ fn hit_test(&self, pipeline_id: Option<PipelineId>, point: WorldPoint, flags: HitTestFlags) -> HitTestResult;
}
/// A hit tester requested to the render backend thread but not necessarily ready yet.
@@ -322,6 +322,17 @@ pub struct HitTestResult {
pub items: Vec<HitTestItem>,
}
+bitflags! {
+ #[derive(Deserialize, MallocSizeOf, Serialize)]
+ ///
+ pub struct HitTestFlags: u8 {
+ ///
+ const FIND_ALL = 0b00000001;
+ ///
+ const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010;
+ }
+}
+
impl Drop for NotificationRequest {
fn drop(&mut self) {
if let Some(ref mut handler) = self.handler {
diff --git a/wrench/src/main.rs b/wrench/src/main.rs
index 7dc037ebb2..843512f595 100644
--- a/wrench/src/main.rs
+++ b/wrench/src/main.rs
@@ -965,6 +965,7 @@ fn render<'a>(
wrench.document_id,
None,
cursor_position,
+ HitTestFlags::empty(),
);
println!("Hit test results:");
diff --git a/wrench/src/rawtest.rs b/wrench/src/rawtest.rs
index 580f1cb015..b86b583617 100644
--- a/wrench/src/rawtest.rs
+++ b/wrench/src/rawtest.rs
@@ -1393,6 +1393,7 @@ impl<'a> RawtestHarness<'a> {
self.wrench.document_id,
None,
point,
+ HitTetFlags::empty(),
)
};

View file

@ -1,62 +0,0 @@
diff --git a/example-compositor/compositor/Cargo.toml b/example-compositor/compositor/Cargo.toml
index 4202332c41..02c6ebe0ce 100644
--- a/example-compositor/compositor/Cargo.toml
+++ b/example-compositor/compositor/Cargo.toml
@@ -7,7 +7,7 @@ license = "MPL-2.0"
[dependencies]
webrender = { path = "../../webrender" }
-gleam = "0.13.1"
+gleam = "0.15"
[target.'cfg(windows)'.dependencies]
compositor-windows = { path = "../compositor-windows" }
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index 556b67d1ed..f1a4718b04 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -57,7 +57,7 @@ debug = ["webrender/capture", "webrender/profiler"]
app_units = "0.7"
env_logger = "0.5"
euclid = "0.22"
-gleam = "0.13"
+gleam = "0.15"
glutin = "0.21"
rayon = "1"
webrender = { path = "../webrender" }
diff --git a/swgl/Cargo.toml b/swgl/Cargo.toml
index 9b7624b13e..2e84e2267c 100644
--- a/swgl/Cargo.toml
+++ b/swgl/Cargo.toml
@@ -12,4 +12,4 @@ glsl-to-cxx = { path = "../glsl-to-cxx" }
webrender_build = { path = "../webrender_build" }
[dependencies]
-gleam = "0.13.1"
+gleam = "0.15"
diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml
index 40064a1573..e82ac85718 100644
--- a/webrender/Cargo.toml
+++ b/webrender/Cargo.toml
@@ -33,7 +33,7 @@ byteorder = "1.0"
cstr = "0.2"
euclid = { version = "0.22.0", features = ["serde"] }
fxhash = "0.2.1"
-gleam = "0.13.1"
+gleam = "0.15"
lazy_static = "1"
log = "0.4"
malloc_size_of_derive = "0.1"
diff --git a/wrench/Cargo.toml b/wrench/Cargo.toml
index 7d2345be43..f51e4202ff 100644
--- a/wrench/Cargo.toml
+++ b/wrench/Cargo.toml
@@ -9,7 +9,7 @@ edition = "2018"
[dependencies]
base64 = "0.12"
env_logger = { version = "0.5", optional = true }
-gleam = "0.13"
+gleam = "0.15"
glutin = "0.21"
clap = { version = "2", features = ["yaml"] }
log = "0.4"

View file

@ -1,221 +0,0 @@
diff --git a/webrender/build.rs b/webrender/build.rs
index 60b4a96c23..adc5dbf2e8 100644
--- a/webrender/build.rs
+++ b/webrender/build.rs
@@ -2,8 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-extern crate webrender_build;
-
use std::borrow::Cow;
use std::env;
use std::fs::{canonicalize, read_dir, File};
diff --git a/webrender/src/device/gl.rs b/webrender/src/device/gl.rs
index eb272e56d6..5e0c050378 100644
--- a/webrender/src/device/gl.rs
+++ b/webrender/src/device/gl.rs
@@ -386,6 +386,7 @@ impl<T> Drop for VBO<T> {
pub struct ExternalTexture {
id: gl::GLuint,
target: gl::GLuint,
+ #[allow(dead_code)]
swizzle: Swizzle,
uv_rect: TexelRect,
}
diff --git a/webrender/src/hit_test.rs b/webrender/src/hit_test.rs
index e095d8db0c..0bd02cd426 100644
--- a/webrender/src/hit_test.rs
+++ b/webrender/src/hit_test.rs
@@ -6,10 +6,9 @@ use api::{BorderRadius, ClipMode, HitTestItem, HitTestResult, ItemTag, Primitive
use api::{PipelineId, ApiHitTester, ClipId};
use api::units::*;
use crate::clip::{ClipItemKind, ClipStore, ClipNode, rounded_rectangle_contains_point};
-use crate::clip::{polygon_contains_point};
+use crate::clip::polygon_contains_point;
use crate::prim_store::PolygonKey;
use crate::scene_builder_thread::Interners;
-use crate::spatial_node::SpatialNodeType;
use crate::spatial_tree::{SpatialNodeIndex, SpatialTree};
use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo};
use std::ops;
diff --git a/webrender/src/internal_types.rs b/webrender/src/internal_types.rs
index 07f937c6a0..9819850db0 100644
--- a/webrender/src/internal_types.rs
+++ b/webrender/src/internal_types.rs
@@ -536,6 +536,7 @@ pub enum ResultMsg {
#[derive(Clone, Debug)]
pub struct ResourceCacheError {
+ #[allow(dead_code)]
description: String,
}
diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs
index 34bc9fb49b..cc200e30eb 100644
--- a/webrender/src/lib.rs
+++ b/webrender/src/lib.rs
@@ -71,8 +71,7 @@ extern crate serde;
extern crate tracy_rs;
#[macro_use]
extern crate derive_more;
-extern crate malloc_size_of;
-extern crate svg_fmt;
+use malloc_size_of;
#[macro_use]
mod profiler;
@@ -163,41 +162,14 @@ mod platform {
}
}
-#[cfg(target_os = "macos")]
-extern crate core_foundation;
-#[cfg(target_os = "macos")]
-extern crate core_graphics;
-#[cfg(target_os = "macos")]
-extern crate core_text;
-
-#[cfg(all(unix, not(target_os = "macos")))]
-extern crate freetype;
-#[cfg(all(unix, not(target_os = "macos")))]
-extern crate libc;
-
-#[cfg(target_os = "windows")]
-extern crate dwrote;
-
-extern crate bincode;
-extern crate byteorder;
-pub extern crate euclid;
-extern crate fxhash;
-extern crate gleam;
-extern crate num_traits;
-extern crate plane_split;
-extern crate rayon;
-#[cfg(feature = "ron")]
-extern crate ron;
+pub use euclid;
#[macro_use]
extern crate smallvec;
-extern crate time;
-#[cfg(all(feature = "capture", feature = "png"))]
-extern crate png;
#[cfg(test)]
-extern crate rand;
+use rand;
-pub extern crate api;
-extern crate webrender_build;
+pub use api;
+use webrender_build;
#[doc(hidden)]
pub use crate::composite::{CompositorConfig, Compositor, CompositorCapabilities, CompositorSurfaceTransform};
diff --git a/webrender/src/profiler.rs b/webrender/src/profiler.rs
index 40c091d617..702fa634ff 100644
--- a/webrender/src/profiler.rs
+++ b/webrender/src/profiler.rs
@@ -1362,6 +1362,7 @@ pub struct Counter {
change_indicator: u8,
/// Only used to check that the constants match the real index.
+ #[allow(dead_code)]
index: usize,
graph: Option<Graph>,
diff --git a/webrender/src/render_target.rs b/webrender/src/render_target.rs
index d31176047b..301ce4ec4d 100644
--- a/webrender/src/render_target.rs
+++ b/webrender/src/render_target.rs
@@ -10,7 +10,7 @@ use crate::batch::{ClipBatcher, BatchBuilder};
use crate::spatial_tree::{SpatialTree, ROOT_SPATIAL_NODE_INDEX};
use crate::clip::ClipStore;
use crate::composite::CompositeState;
-use crate::frame_builder::{FrameGlobalResources};
+use crate::frame_builder::FrameGlobalResources;
use crate::gpu_cache::{GpuCache, GpuCacheAddress};
use crate::gpu_types::{BorderInstance, SvgFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
use crate::gpu_types::{TransformPalette, ZBufferIdGenerator};
@@ -819,10 +819,10 @@ fn add_svg_filter_instances(
let generic_int = match filter {
SvgFilterInfo::Blend(mode) => *mode as u16,
SvgFilterInfo::ComponentTransfer(data) =>
- ((data.r_func.to_int() << 12 |
+ (data.r_func.to_int() << 12 |
data.g_func.to_int() << 8 |
data.b_func.to_int() << 4 |
- data.a_func.to_int()) as u16),
+ data.a_func.to_int()) as u16,
SvgFilterInfo::Composite(operator) =>
operator.as_int() as u16,
SvgFilterInfo::LinearToSrgb |
diff --git a/webrender_api/src/image.rs b/webrender_api/src/image.rs
index 238d004814..23c660b647 100644
--- a/webrender_api/src/image.rs
+++ b/webrender_api/src/image.rs
@@ -326,10 +326,9 @@ pub enum ImageData {
}
mod serde_image_data_raw {
- extern crate serde_bytes;
-
- use std::sync::Arc;
use serde::{Deserializer, Serializer};
+ use serde_bytes;
+ use std::sync::Arc;
pub fn serialize<S: Serializer>(bytes: &Arc<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error> {
serde_bytes::serialize(bytes.as_slice(), serializer)
diff --git a/webrender_api/src/lib.rs b/webrender_api/src/lib.rs
index b0446c1d0b..f5cda1fbf6 100644
--- a/webrender_api/src/lib.rs
+++ b/webrender_api/src/lib.rs
@@ -15,29 +15,20 @@
#![cfg_attr(feature = "cargo-clippy", allow(clippy::float_cmp, clippy::too_many_arguments))]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::unreadable_literal, clippy::new_without_default))]
-pub extern crate crossbeam_channel;
-pub extern crate euclid;
+pub use crossbeam_channel;
+pub use euclid;
-extern crate app_units;
#[macro_use]
extern crate bitflags;
-extern crate byteorder;
#[cfg(feature = "nightly")]
extern crate core;
-#[cfg(target_os = "macos")]
-extern crate core_foundation;
-#[cfg(target_os = "macos")]
-extern crate core_graphics;
-extern crate derive_more;
#[macro_use]
extern crate malloc_size_of_derive;
-extern crate serde;
#[macro_use]
extern crate serde_derive;
-extern crate time;
-extern crate malloc_size_of;
-extern crate peek_poke;
+use malloc_size_of;
+use peek_poke;
pub mod channel;
mod color;
diff --git a/wr_malloc_size_of/lib.rs b/wr_malloc_size_of/lib.rs
index 38b049095e..abd982ffe8 100644
--- a/wr_malloc_size_of/lib.rs
+++ b/wr_malloc_size_of/lib.rs
@@ -10,8 +10,8 @@
//! A reduced fork of Firefox's malloc_size_of crate, for bundling with WebRender.
-extern crate app_units;
-extern crate euclid;
+use app_units;
+use euclid;
use std::hash::{BuildHasher, Hash};
use std::mem::size_of;

View file

@ -1,20 +0,0 @@
diff --git a/third_party/webrender/webrender_api/src/display_item_cache.rs b/third_party/webrender/webrender_api/src/display_item_cache.rs
index 169e54797a..8a28ac4ab2 100644
--- a/third_party/webrender/webrender_api/src/display_item_cache.rs
+++ b/third_party/webrender/webrender_api/src/display_item_cache.rs
@@ -58,13 +58,13 @@ pub struct DisplayItemCache {
impl DisplayItemCache {
fn add_item(&mut self, key: ItemKey, item: CachedDisplayItem) {
- let mut entry = &mut self.entries[key as usize];
+ let entry = &mut self.entries[key as usize];
entry.items.push(item);
entry.occupied = true;
}
fn clear_entry(&mut self, key: ItemKey) {
- let mut entry = &mut self.entries[key as usize];
+ let entry = &mut self.entries[key as usize];
entry.items.clear();
entry.occupied = false;
}

View file

@ -1 +0,0 @@
e491e1ae637b2eed1e7195855d88357e5eb3ddf9

View file

@ -1,4 +0,0 @@
001-Restore-hit-testing-api.diff
002-Upgrade-version-of-gleam.diff
003-Fix-WebRender-warnings.diff
004-Fix-more-WebRender-warnings.diff

View file

@ -1,17 +0,0 @@
[package]
name = "peek-poke"
version = "0.2.0"
authors = ["Dan Glastonbury <dan.glastonbury@gmail.com>"]
repository = "https://github.com/servo/webrender"
description = "A mechanism for serializing and deserializing data into/from byte buffers, for use in WebRender."
license = "MIT/Apache-2.0"
edition = "2018"
[dependencies]
euclid = { version = "0.22.0", optional = true }
peek-poke-derive = { version = "0.2", path = "./peek-poke-derive", optional = true }
[features]
default = ["derive"]
derive = ["peek-poke-derive"]
extras = ["derive", "euclid"]

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,44 +0,0 @@
MIT License
Copyright (c) 2019 Daniel Glastonbury
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.
This work incorporates work covered by the following copyright and permission
notice:
Copyright (c) 2019 Devashish Dixit
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,54 +0,0 @@
# Peeks, Pokes, and Pointers
Peek from and poke structures into byte slices.
## Benchmark
Below are the benchmark results of comparison between `peek-poke` and `bincode` serializing and deserializing same `struct`:
```
struct MyPeekPokeStruct {
a: u8,
b: u16,
c: MyPeekPokeEnum,
d: Option<usize>,
}
enum MyPeekPokeEnum {
Variant1,
Variant2(u16),
}
```
```
Benchmarking struct::serialize/peek_poke::poke_into: Collecting 100 samples in struct::serialize/peek_poke::poke_into
time: [2.7267 ns 2.7321 ns 2.7380 ns]
Benchmarking struct::serialize/bincode::serialize: Collecting 100 samples in est struct::serialize/bincode::serialize
time: [31.264 ns 31.326 ns 31.389 ns]
Benchmarking struct::deserialize/peek_poke::peek_from: Collecting 100 samples struct::deserialize/peek_poke::peek_from
time: [5.3544 ns 5.3672 ns 5.3817 ns]
Benchmarking struct::deserialize/bincode::deserialize: Collecting 100 samples in struct::deserialize/bincode::deserialize
time: [25.155 ns 26.439 ns 27.661 ns]
```
You can run benchmarks by running following command:
```
cargo bench
```
## License
[license]: #license
Licensed under either of
- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (http://opensource.org/licenses/MIT)
at your option.
see [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as
defined in the Apache-2.0 license, shall be dual licensed as about, without any additional terms or conditions.

View file

@ -1,19 +0,0 @@
[package]
name = "peek-poke-derive"
version = "0.2.1"
authors = ["Dan Glastonbury <dan.glastonbury@gmail.com>"]
repository = "https://github.com/servo/webrender"
description = "Derive macro for peek-poke."
license = "MIT/Apache-2.0"
edition = "2018"
[lib]
doctest = false
proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = "1"
synstructure = "0.12"
unicode-xid = "0.2"

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,44 +0,0 @@
MIT License
Copyright (c) 2019 Daniel Glastonbury
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.
This work incorporates work covered by the following copyright and permission
notice:
Copyright (c) 2019 Devashish Dixit
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,54 +0,0 @@
# Peeks, Pokes, and Pointers
Peek from and poke structures into byte slices.
## Benchmark
Below are the benchmark results of comparison between `peek-poke` and `bincode` serializing and deserializing same `struct`:
```
struct MyPeekPokeStruct {
a: u8,
b: u16,
c: MyPeekPokeEnum,
d: Option<usize>,
}
enum MyPeekPokeEnum {
Variant1,
Variant2(u16),
}
```
```
Benchmarking struct::serialize/peek_poke::poke_into: Collecting 100 samples in struct::serialize/peek_poke::poke_into
time: [2.7267 ns 2.7321 ns 2.7380 ns]
Benchmarking struct::serialize/bincode::serialize: Collecting 100 samples in est struct::serialize/bincode::serialize
time: [31.264 ns 31.326 ns 31.389 ns]
Benchmarking struct::deserialize/peek_poke::peek_from: Collecting 100 samples struct::deserialize/peek_poke::peek_from
time: [5.3544 ns 5.3672 ns 5.3817 ns]
Benchmarking struct::deserialize/bincode::deserialize: Collecting 100 samples in struct::deserialize/bincode::deserialize
time: [25.155 ns 26.439 ns 27.661 ns]
```
You can run benchmarks by running following command:
```
cargo bench
```
## License
[license]: #license
Licensed under either of
- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (http://opensource.org/licenses/MIT)
at your option.
see [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as
defined in the Apache-2.0 license, shall be dual licensed as about, without any additional terms or conditions.

View file

@ -1,263 +0,0 @@
// Copyright 2019 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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 proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Ident, Index, TraitBound};
use synstructure::{decl_derive, Structure, BindStyle, AddBounds};
use unicode_xid::UnicodeXID;
// Internal method for sanitizing an identifier for hygiene purposes.
fn sanitize_ident(s: &str) -> Ident {
let mut res = String::with_capacity(s.len());
for mut c in s.chars() {
if !UnicodeXID::is_xid_continue(c) {
c = '_'
}
// Deduplicate consecutive _ characters.
if res.ends_with('_') && c == '_' {
continue;
}
res.push(c);
}
Ident::new(&res, Span::call_site())
}
/// Calculates size type for number of variants (used for enums)
fn get_discriminant_size_type(len: usize) -> TokenStream {
if len <= <u8>::max_value() as usize {
quote! { u8 }
} else if len <= <u16>::max_value() as usize {
quote! { u16 }
} else {
quote! { u32 }
}
}
fn is_struct(s: &Structure) -> bool {
// a single variant with no prefix is 'struct'
matches!(&s.variants()[..], [v] if v.prefix.is_none())
}
fn derive_max_size(s: &Structure) -> TokenStream {
let max_size = s.variants().iter().fold(quote!(0), |acc, vi| {
let variant_size = vi.bindings().iter().fold(quote!(0), |acc, bi| {
// compute size of each variant by summing the sizes of its bindings
let ty = &bi.ast().ty;
quote!(#acc + <#ty>::max_size())
});
// find the maximum of each variant
quote! {
max(#acc, #variant_size)
}
});
let body = if is_struct(s) {
max_size
} else {
let discriminant_size_type = get_discriminant_size_type(s.variants().len());
quote! {
#discriminant_size_type ::max_size() + #max_size
}
};
quote! {
#[inline(always)]
fn max_size() -> usize {
use std::cmp::max;
#body
}
}
}
fn derive_peek_from_for_enum(s: &mut Structure) -> TokenStream {
assert!(!is_struct(s));
s.bind_with(|_| BindStyle::Move);
let num_variants = s.variants().len();
let discriminant_size_type = get_discriminant_size_type(num_variants);
let body = s
.variants()
.iter()
.enumerate()
.fold(quote!(), |acc, (i, vi)| {
let bindings = vi
.bindings()
.iter()
.map(|bi| quote!(#bi))
.collect::<Vec<_>>();
let variant_pat = Index::from(i);
let poke_exprs = bindings.iter().fold(quote!(), |acc, bi| {
quote! {
#acc
let (#bi, bytes) = peek_poke::peek_from_default(bytes);
}
});
let construct = vi.construct(|_, i| {
let bi = &bindings[i];
quote!(#bi)
});
quote! {
#acc
#variant_pat => {
#poke_exprs
*output = #construct;
bytes
}
}
});
let type_name = s.ast().ident.to_string();
let max_tag_value = num_variants - 1;
quote! {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let (variant, bytes) = peek_poke::peek_from_default::<#discriminant_size_type>(bytes);
match variant {
#body
out_of_range_tag => {
panic!("WRDL: memory corruption detected while parsing {} - enum tag should be <= {}, but was {}",
#type_name, #max_tag_value, out_of_range_tag);
}
}
}
}
}
fn derive_peek_from_for_struct(s: &mut Structure) -> TokenStream {
assert!(is_struct(&s));
s.variants_mut()[0].bind_with(|_| BindStyle::RefMut);
let pat = s.variants()[0].pat();
let peek_exprs = s.variants()[0].bindings().iter().fold(quote!(), |acc, bi| {
let ty = &bi.ast().ty;
quote! {
#acc
let bytes = <#ty>::peek_from(bytes, #bi);
}
});
let body = quote! {
#pat => {
#peek_exprs
bytes
}
};
quote! {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
match &mut (*output) {
#body
}
}
}
}
fn derive_poke_into(s: &Structure) -> TokenStream {
let is_struct = is_struct(&s);
let discriminant_size_type = get_discriminant_size_type(s.variants().len());
let body = s
.variants()
.iter()
.enumerate()
.fold(quote!(), |acc, (i, vi)| {
let init = if !is_struct {
let index = Index::from(i);
quote! {
let bytes = #discriminant_size_type::poke_into(&#index, bytes);
}
} else {
quote!()
};
let variant_pat = vi.pat();
let poke_exprs = vi.bindings().iter().fold(init, |acc, bi| {
quote! {
#acc
let bytes = #bi.poke_into(bytes);
}
});
quote! {
#acc
#variant_pat => {
#poke_exprs
bytes
}
}
});
quote! {
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
match &*self {
#body
}
}
}
}
fn peek_poke_derive(mut s: Structure) -> TokenStream {
s.binding_name(|_, i| Ident::new(&format!("__self_{}", i), Span::call_site()));
let max_size_fn = derive_max_size(&s);
let poke_into_fn = derive_poke_into(&s);
let peek_from_fn = if is_struct(&s) {
derive_peek_from_for_struct(&mut s)
} else {
derive_peek_from_for_enum(&mut s)
};
let poke_impl = s.gen_impl(quote! {
extern crate peek_poke;
gen unsafe impl peek_poke::Poke for @Self {
#max_size_fn
#poke_into_fn
}
});
// To implement `fn peek_from` we require that types implement `Default`
// trait to create temporary values. This code does the addition all
// manually until https://github.com/mystor/synstructure/issues/24 is fixed.
let default_trait = syn::parse_str::<TraitBound>("::std::default::Default").unwrap();
let peek_trait = syn::parse_str::<TraitBound>("peek_poke::Peek").unwrap();
let ast = s.ast();
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let mut where_clause = where_clause.cloned();
s.add_trait_bounds(&default_trait, &mut where_clause, AddBounds::Generics);
s.add_trait_bounds(&peek_trait, &mut where_clause, AddBounds::Generics);
let dummy_const: Ident = sanitize_ident(&format!("_DERIVE_peek_poke_Peek_FOR_{}", name));
let peek_impl = quote! {
#[allow(non_upper_case_globals)]
const #dummy_const: () = {
extern crate peek_poke;
impl #impl_generics peek_poke::Peek for #name #ty_generics #where_clause {
#peek_from_fn
}
};
};
quote! {
#poke_impl
#peek_impl
}
}
decl_derive!([PeekPoke] => peek_poke_derive);

View file

@ -1,191 +0,0 @@
// Copyright 2019 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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::{Peek, Poke};
use euclid::{Point2D, Rect, Box2D, SideOffsets2D, Size2D, Transform3D, Vector2D};
unsafe impl<T: Poke, U> Poke for Point2D<T, U> {
#[inline(always)]
fn max_size() -> usize {
2 * T::max_size()
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
let bytes = self.x.poke_into(bytes);
let bytes = self.y.poke_into(bytes);
bytes
}
}
impl<T: Peek, U> Peek for Point2D<T, U> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let bytes = T::peek_from(bytes, &mut (*output).x);
let bytes = T::peek_from(bytes, &mut (*output).y);
bytes
}
}
unsafe impl<T: Poke, U> Poke for Rect<T, U> {
#[inline(always)]
fn max_size() -> usize {
Point2D::<T, U>::max_size() + Size2D::<T, U>::max_size()
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
let bytes = self.origin.poke_into(bytes);
let bytes = self.size.poke_into(bytes);
bytes
}
}
impl<T: Peek, U> Peek for Rect<T, U> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let bytes = Point2D::<T, U>::peek_from(bytes, &mut (*output).origin);
let bytes = Size2D::<T, U>::peek_from(bytes, &mut (*output).size);
bytes
}
}
unsafe impl<T: Poke, U> Poke for Box2D<T, U> {
#[inline(always)]
fn max_size() -> usize {
Point2D::<T, U>::max_size() * 2
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
let bytes = self.min.poke_into(bytes);
let bytes = self.max.poke_into(bytes);
bytes
}
}
impl<T: Peek, U> Peek for Box2D<T, U> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let bytes = Point2D::<T, U>::peek_from(bytes, &mut (*output).min);
let bytes = Point2D::<T, U>::peek_from(bytes, &mut (*output).max);
bytes
}
}
unsafe impl<T: Poke, U> Poke for SideOffsets2D<T, U> {
#[inline(always)]
fn max_size() -> usize {
4 * T::max_size()
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
let bytes = self.top.poke_into(bytes);
let bytes = self.right.poke_into(bytes);
let bytes = self.bottom.poke_into(bytes);
let bytes = self.left.poke_into(bytes);
bytes
}
}
impl<T: Peek, U> Peek for SideOffsets2D<T, U> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let bytes = T::peek_from(bytes, &mut (*output).top);
let bytes = T::peek_from(bytes, &mut (*output).right);
let bytes = T::peek_from(bytes, &mut (*output).bottom);
let bytes = T::peek_from(bytes, &mut (*output).left);
bytes
}
}
unsafe impl<T: Poke, U> Poke for Size2D<T, U> {
#[inline(always)]
fn max_size() -> usize {
2 * T::max_size()
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
let bytes = self.width.poke_into(bytes);
let bytes = self.height.poke_into(bytes);
bytes
}
}
impl<T: Peek, U> Peek for Size2D<T, U> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let bytes = T::peek_from(bytes, &mut (*output).width);
let bytes = T::peek_from(bytes, &mut (*output).height);
bytes
}
}
unsafe impl<T: Poke, S, D> Poke for Transform3D<T, S, D> {
#[inline(always)]
fn max_size() -> usize {
16 * T::max_size()
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
let bytes = self.m11.poke_into(bytes);
let bytes = self.m12.poke_into(bytes);
let bytes = self.m13.poke_into(bytes);
let bytes = self.m14.poke_into(bytes);
let bytes = self.m21.poke_into(bytes);
let bytes = self.m22.poke_into(bytes);
let bytes = self.m23.poke_into(bytes);
let bytes = self.m24.poke_into(bytes);
let bytes = self.m31.poke_into(bytes);
let bytes = self.m32.poke_into(bytes);
let bytes = self.m33.poke_into(bytes);
let bytes = self.m34.poke_into(bytes);
let bytes = self.m41.poke_into(bytes);
let bytes = self.m42.poke_into(bytes);
let bytes = self.m43.poke_into(bytes);
let bytes = self.m44.poke_into(bytes);
bytes
}
}
impl<T: Peek, S, D> Peek for Transform3D<T, S, D> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let bytes = T::peek_from(bytes, &mut (*output).m11);
let bytes = T::peek_from(bytes, &mut (*output).m12);
let bytes = T::peek_from(bytes, &mut (*output).m13);
let bytes = T::peek_from(bytes, &mut (*output).m14);
let bytes = T::peek_from(bytes, &mut (*output).m21);
let bytes = T::peek_from(bytes, &mut (*output).m22);
let bytes = T::peek_from(bytes, &mut (*output).m23);
let bytes = T::peek_from(bytes, &mut (*output).m24);
let bytes = T::peek_from(bytes, &mut (*output).m31);
let bytes = T::peek_from(bytes, &mut (*output).m32);
let bytes = T::peek_from(bytes, &mut (*output).m33);
let bytes = T::peek_from(bytes, &mut (*output).m34);
let bytes = T::peek_from(bytes, &mut (*output).m41);
let bytes = T::peek_from(bytes, &mut (*output).m42);
let bytes = T::peek_from(bytes, &mut (*output).m43);
let bytes = T::peek_from(bytes, &mut (*output).m44);
bytes
}
}
unsafe impl<T: Poke, U> Poke for Vector2D<T, U> {
#[inline(always)]
fn max_size() -> usize {
2 * T::max_size()
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
let bytes = self.x.poke_into(bytes);
let bytes = self.y.poke_into(bytes);
bytes
}
}
impl<T: Peek, U> Peek for Vector2D<T, U> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let bytes = T::peek_from(bytes, &mut (*output).x);
let bytes = T::peek_from(bytes, &mut (*output).y);
bytes
}
}

View file

@ -1,441 +0,0 @@
// Copyright 2019 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.
//! Fast binary serialization and deserialization for types with a known maximum size.
//!
//! ## Binary Encoding Scheme
//!
//! ## Usage
//!
//! ## Comparison to bincode
#[cfg(feature = "derive")]
pub use peek_poke_derive::*;
use core::{marker::PhantomData, mem::size_of, slice};
use crate::{slice_ext::*, vec_ext::*};
mod slice_ext;
mod vec_ext;
union MaybeUninitShim<T: Copy> {
uninit: (),
init: T,
}
/// Peek helper for constructing a `T` by `Copy`ing into an uninitialized stack
/// allocation.
pub unsafe fn peek_from_uninit<T: Copy + Peek>(bytes: *const u8) -> (T, *const u8) {
let mut val = MaybeUninitShim { uninit: () };
let bytes = <T>::peek_from(bytes, &mut val.init);
(val.init, bytes)
}
/// Peek helper for constructing a `T` by `Default` initialized stack
/// allocation.
pub unsafe fn peek_from_default<T: Default + Peek>(bytes: *const u8) -> (T, *const u8) {
let mut val = T::default();
let bytes = <T>::peek_from(bytes, &mut val);
(val, bytes)
}
/// Peek inplace a `T` from a slice of bytes, returning a slice of the remaining
/// bytes. `src` must contain at least `T::max_size()` bytes.
///
/// [`ensure_red_zone`] can be used to add required padding.
pub fn peek_from_slice<'a, T: Peek>(src: &'a [u8], dst: &mut T) -> &'a [u8] {
unsafe {
// If src.len() == T::max_size() then src is at the start of the red-zone.
assert!(T::max_size() < src.len(), "WRDL: unexpected end of display list");
let end_ptr = T::peek_from(src.as_ptr(), dst);
let len = end_ptr as usize - src.as_ptr() as usize;
// Did someone break the T::peek_from() can't read more than T::max_size()
// bytes contract?
assert!(len <= src.len(), "WRDL: Peek::max_size was wrong");
slice::from_raw_parts(end_ptr, src.len() - len)
}
}
/// Poke helper to insert a serialized version of `src` at the beginning for `dst`.
pub fn poke_inplace_slice<T: Poke>(src: &T, dst: &mut [u8]) {
assert!(T::max_size() <= dst.len(), "WRDL: buffer too small to write into");
unsafe {
src.poke_into(dst.as_mut_ptr());
}
}
/// Poke helper to append a serialized version of `src` to the end of `dst`.
pub fn poke_into_vec<T: Poke>(src: &T, dst: &mut Vec<u8>) {
dst.reserve(T::max_size());
unsafe {
let ptr = dst.as_end_mut_ptr();
let end_ptr = src.poke_into(ptr);
dst.set_end_ptr(end_ptr);
}
}
// TODO: Is returning the len of the iterator of any practical use?
pub fn poke_extend_vec<I>(src: I, dst: &mut Vec<u8>) -> usize
where
I: ExactSizeIterator,
I::Item: Poke,
{
let len = src.len();
let max_size = len * I::Item::max_size();
dst.reserve(max_size);
unsafe {
let ptr = dst.as_end_mut_ptr();
// Guard against the possibility of a misbehaved implementation of
// ExactSizeIterator by writing at most `len` items.
let end_ptr = src.take(len).fold(ptr, |ptr, item| item.poke_into(ptr));
dst.set_end_ptr(end_ptr);
}
len
}
/// Add `T::max_size()` "red zone" (padding of zeroes) to the end of the vec of
/// `bytes`. This allows deserialization to assert that at least `T::max_size()`
/// bytes exist at all times.
pub fn ensure_red_zone<T: Poke>(bytes: &mut Vec<u8>) {
bytes.reserve(T::max_size());
unsafe {
let end_ptr = bytes.as_end_mut_ptr();
end_ptr.write_bytes(0, T::max_size());
bytes.set_end_ptr(end_ptr.add(T::max_size()));
}
}
/// Remove the "red zone" (padding of zeroes) from the end of the vec of `bytes`.
/// This is effectively the inverse of `ensure_red_zone`, with the caveat that
/// space reserved for the red zone is not un-reserved. Callers are repsonsible
/// for making sure the vec actually has a red zone, otherwise data bytes can
/// get stripped instead.
pub fn strip_red_zone<T: Poke>(bytes: &mut Vec<u8>) {
assert!(bytes.len() >= T::max_size());
unsafe {
let end_ptr = bytes.as_end_mut_ptr();
end_ptr.write_bytes(0, T::max_size());
bytes.set_end_ptr(end_ptr.sub(T::max_size()));
}
}
#[inline]
unsafe fn read_verbatim<T>(src: *const u8, dst: *mut T) -> *const u8 {
*dst = (src as *const T).read_unaligned();
src.add(size_of::<T>())
}
#[inline]
unsafe fn write_verbatim<T>(src: T, dst: *mut u8) -> *mut u8 {
(dst as *mut T).write_unaligned(src);
dst.add(size_of::<T>())
}
#[cfg(feature = "extras")]
mod euclid;
/// A trait for values that provide serialization into buffers of bytes.
///
/// # Example
///
/// ```no_run
/// use peek_poke::Poke;
///
/// struct Bar {
/// a: u32,
/// b: u8,
/// c: i16,
/// }
///
/// unsafe impl Poke for Bar {
/// fn max_size() -> usize {
/// <u32>::max_size() + <u8>::max_size() + <i16>::max_size()
/// }
/// unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
/// let bytes = self.a.poke_into(bytes);
/// let bytes = self.b.poke_into(bytes);
/// self.c.poke_into(bytes)
/// }
/// }
/// ```
///
/// # Safety
///
/// The `Poke` trait is an `unsafe` trait for the reasons, and implementors must
/// ensure that they adhere to these contracts:
///
/// * `max_size()` query and calculations in general must be correct. Callers
/// of this trait are expected to rely on the contract defined on each
/// method, and implementors must ensure such contracts remain true.
pub unsafe trait Poke {
/// Return the maximum number of bytes that the serialized version of `Self`
/// will occupy.
///
/// # Safety
///
/// Implementors of `Poke` guarantee to not write more than the result of
/// calling `max_size()` into the buffer pointed to by `bytes` when
/// `poke_into()` is called.
fn max_size() -> usize;
/// Serialize into the buffer pointed to by `bytes`.
///
/// Returns a pointer to the next byte after the serialized representation of `Self`.
///
/// # Safety
///
/// This function is unsafe because undefined behavior can result if the
/// caller does not ensure all of the following:
///
/// * `bytes` must denote a valid pointer to a block of memory.
///
/// * `bytes` must pointer to at least the number of bytes returned by
/// `max_size()`.
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8;
}
/// A trait for values that provide deserialization from buffers of bytes.
///
/// # Example
///
/// ```ignore
/// use peek_poke::Peek;
///
/// struct Bar {
/// a: u32,
/// b: u8,
/// c: i16,
/// }
///
/// ...
///
/// impl Peek for Bar {
/// unsafe fn peek_from(&mut self, bytes: *const u8) -> *const u8 {
/// let bytes = self.a.peek_from(bytes);
/// let bytes = self.b.peek_from(bytes);
/// self.c.peek_from(bytes)
/// }
/// }
/// ```
///
/// # Safety
///
/// The `Peek` trait contains unsafe methods for the following reasons, and
/// implementors must ensure that they adhere to these contracts:
///
/// * Callers of this trait are expected to rely on the contract defined on each
/// method, and implementors must ensure that `peek_from()` doesn't read more
/// bytes from `bytes` than is returned by `Peek::max_size()`.
pub trait Peek: Poke {
/// Deserialize from the buffer pointed to by `bytes`.
///
/// Returns a pointer to the next byte after the unconsumed bytes not used
/// to deserialize the representation of `Self`.
///
/// # Safety
///
/// This function is unsafe because undefined behavior can result if the
/// caller does not ensure all of the following:
///
/// * `bytes` must denote a valid pointer to a block of memory.
///
/// * `bytes` must pointer to at least the number of bytes returned by
/// `Poke::max_size()`.
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8;
}
macro_rules! impl_poke_for_deref {
(<$($desc:tt)+) => {
unsafe impl <$($desc)+ {
#[inline(always)]
fn max_size() -> usize {
<T>::max_size()
}
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
(**self).poke_into(bytes)
}
}
}
}
impl_poke_for_deref!(<'a, T: Poke> Poke for &'a T);
impl_poke_for_deref!(<'a, T: Poke> Poke for &'a mut T);
macro_rules! impl_for_primitive {
($($ty:ty)+) => {
$(unsafe impl Poke for $ty {
#[inline(always)]
fn max_size() -> usize {
size_of::<Self>()
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
write_verbatim(*self, bytes)
}
}
impl Peek for $ty {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
read_verbatim(bytes, output)
}
})+
};
}
impl_for_primitive! {
i8 i16 i32 i64 isize
u8 u16 u32 u64 usize
f32 f64
}
unsafe impl Poke for bool {
#[inline(always)]
fn max_size() -> usize {
u8::max_size()
}
#[inline]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
(*self as u8).poke_into(bytes)
}
}
impl Peek for bool {
#[inline]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let mut int_bool = 0u8;
let ptr = <u8>::peek_from(bytes, &mut int_bool);
*output = int_bool != 0;
ptr
}
}
unsafe impl<T> Poke for PhantomData<T> {
#[inline(always)]
fn max_size() -> usize {
0
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
bytes
}
}
impl<T> Peek for PhantomData<T> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
*output = PhantomData;
bytes
}
}
unsafe impl<T: Poke> Poke for Option<T> {
#[inline(always)]
fn max_size() -> usize {
u8::max_size() + T::max_size()
}
#[inline]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
match self {
None => 0u8.poke_into(bytes),
Some(ref v) => {
let bytes = 1u8.poke_into(bytes);
let bytes = v.poke_into(bytes);
bytes
}
}
}
}
impl<T: Default + Peek> Peek for Option<T> {
#[inline]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let (variant, bytes) = peek_from_default::<u8>(bytes);
match variant {
0 => {
*output = None;
bytes
}
1 => {
let (val, bytes) = peek_from_default(bytes);
*output = Some(val);
bytes
}
_ => unreachable!(),
}
}
}
macro_rules! impl_for_arrays {
($($len:tt)+) => {
$(unsafe impl<T: Poke> Poke for [T; $len] {
fn max_size() -> usize {
$len * T::max_size()
}
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
self.iter().fold(bytes, |bytes, e| e.poke_into(bytes))
}
}
impl<T: Peek> Peek for [T; $len] {
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
(&mut *output).iter_mut().fold(bytes, |bytes, e| <T>::peek_from(bytes, e))
}
})+
}
}
impl_for_arrays! {
01 02 03 04 05 06 07 08 09 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32
}
unsafe impl Poke for () {
fn max_size() -> usize {
0
}
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
bytes
}
}
impl Peek for () {
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
*output = ();
bytes
}
}
macro_rules! impl_for_tuple {
($($n:tt: $ty:ident),+) => {
unsafe impl<$($ty: Poke),+> Poke for ($($ty,)+) {
#[inline(always)]
fn max_size() -> usize {
0 $(+ <$ty>::max_size())+
}
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
$(let bytes = self.$n.poke_into(bytes);)+
bytes
}
}
impl<$($ty: Peek),+> Peek for ($($ty,)+) {
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
$(let bytes = $ty::peek_from(bytes, &mut (*output).$n);)+
bytes
}
}
}
}
impl_for_tuple!(0: A);
impl_for_tuple!(0: A, 1: B);
impl_for_tuple!(0: A, 1: B, 2: C);
impl_for_tuple!(0: A, 1: B, 2: C, 3: D);
impl_for_tuple!(0: A, 1: B, 2: C, 3: D, 4: E);

View file

@ -1,19 +0,0 @@
// Copyright 2019 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.
pub trait AsEndMutPtr<T> {
fn as_end_mut_ptr(self) -> *mut T;
}
impl<'a> AsEndMutPtr<u8> for &'a mut [u8] {
fn as_end_mut_ptr(self) -> *mut u8 {
unsafe { self.as_mut_ptr().add(self.len()) }
}
}

View file

@ -1,26 +0,0 @@
// Copyright 2019 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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::vec::Vec;
pub trait VecExt {
type Item;
unsafe fn set_end_ptr(&mut self, end: *const Self::Item);
}
impl<T> VecExt for Vec<T> {
type Item = T;
unsafe fn set_end_ptr(&mut self, end: *const T) {
assert!(end as usize >= self.as_ptr() as usize);
let new_len = end as usize - self.as_ptr() as usize;
assert!(new_len <= self.capacity());
self.set_len(new_len);
}
}

View file

@ -1,117 +0,0 @@
// Copyright 2019 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.
#![allow(dead_code)]
use peek_poke::{PeekPoke, Poke};
use std::{marker::PhantomData, mem::size_of};
#[test]
fn test_numbers() {
assert_eq!(u8::max_size(), size_of::<u8>());
assert_eq!(u16::max_size(), size_of::<u16>());
assert_eq!(u32::max_size(), size_of::<u32>());
assert_eq!(u64::max_size(), size_of::<u64>());
assert_eq!(usize::max_size(), size_of::<usize>());
assert_eq!(i8::max_size(), size_of::<i8>());
assert_eq!(i16::max_size(), size_of::<i16>());
assert_eq!(i32::max_size(), size_of::<i32>());
assert_eq!(i64::max_size(), size_of::<i64>());
assert_eq!(isize::max_size(), size_of::<isize>());
// floating
assert_eq!(f32::max_size(), size_of::<f32>());
assert_eq!(f64::max_size(), size_of::<f64>());
}
#[test]
fn test_bool() {
assert_eq!(bool::max_size(), size_of::<u8>());
}
#[test]
fn test_option() {
assert_eq!(
Option::<usize>::max_size(),
<u8>::max_size() + <usize>::max_size()
);
}
#[test]
fn test_fixed_size_array() {
assert_eq!(<[u32; 32]>::max_size(), 32 * size_of::<u32>());
assert_eq!(<[u64; 8]>::max_size(), 8 * size_of::<u64>());
assert_eq!(<[u8; 19]>::max_size(), 19 * size_of::<u8>());
}
#[test]
fn test_tuple() {
assert_eq!(<(isize, )>::max_size(), size_of::<isize>());
assert_eq!(<(isize, isize, isize)>::max_size(), 3 * size_of::<isize>());
assert_eq!(<(isize, ())>::max_size(), size_of::<isize>());
}
#[test]
fn test_basic_struct() {
#[derive(Debug, PeekPoke)]
struct Bar {
a: u32,
b: u32,
c: u32,
}
assert_eq!(<Bar>::max_size(), 3 * <u32>::max_size());
}
#[test]
fn test_enum() {
#[derive(Clone, Copy, PeekPoke)]
enum TestEnum {
NoArg,
OneArg(usize),
Args(usize, usize),
AnotherNoArg,
StructLike { x: usize, y: f32 },
}
assert_eq!(
TestEnum::max_size(),
<u8>::max_size() + 2 * <usize>::max_size()
);
}
#[test]
fn test_enum_cstyle() {
#[repr(u32)]
#[derive(Clone, Copy, PeekPoke)]
enum BorderStyle {
None = 0,
Solid = 1,
Double = 2,
Dotted = 3,
Dashed = 4,
Hidden = 5,
Groove = 6,
Ridge = 7,
Inset = 8,
Outset = 9,
}
assert_eq!(BorderStyle::max_size(), <u8>::max_size());
}
#[test]
fn test_phantom_data() {
struct Bar;
#[derive(PeekPoke)]
struct Foo {
x: u32,
y: u32,
_marker: PhantomData<Bar>,
}
assert_eq!(Foo::max_size(), 2 * size_of::<u32>())
}

View file

@ -1,275 +0,0 @@
// Copyright 2019 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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 peek_poke::{Peek, PeekPoke, Poke};
use std::{fmt::Debug, marker::PhantomData};
fn poke_into<V: Peek + Poke>(a: &V) -> Vec<u8> {
let mut v = <Vec<u8>>::with_capacity(<V>::max_size());
let end_ptr = unsafe { a.poke_into(v.as_mut_ptr()) };
let new_size = end_ptr as usize - v.as_ptr() as usize;
assert!(new_size <= v.capacity());
unsafe {
v.set_len(new_size);
}
v
}
#[cfg(not(feature = "option_copy"))]
fn the_same<V>(a: V)
where
V: Debug + Default + PartialEq + Peek + Poke,
{
let v = poke_into(&a);
let (b, end_ptr) = unsafe { peek_poke::peek_from_default(v.as_ptr()) };
let size = end_ptr as usize - v.as_ptr() as usize;
assert_eq!(size, v.len());
assert_eq!(a, b);
}
#[cfg(feature = "option_copy")]
fn the_same<V>(a: V)
where
V: Copy + Debug + PartialEq + Peek + Poke,
{
let v = poke_into(&a);
let mut b = a;
let end_ptr = unsafe { b.peek_from(v.as_ptr()) };
let size = end_ptr as usize - v.as_ptr() as usize;
assert_eq!(size, v.len());
assert_eq!(a, b);
}
#[test]
fn test_numbers() {
// unsigned positive
the_same(5u8);
the_same(5u16);
the_same(5u32);
the_same(5u64);
the_same(5usize);
// signed positive
the_same(5i8);
the_same(5i16);
the_same(5i32);
the_same(5i64);
the_same(5isize);
// signed negative
the_same(-5i8);
the_same(-5i16);
the_same(-5i32);
the_same(-5i64);
the_same(-5isize);
// floating
the_same(-100f32);
the_same(0f32);
the_same(5f32);
the_same(-100f64);
the_same(5f64);
}
#[test]
fn test_bool() {
the_same(true);
the_same(false);
}
#[cfg(any(feature = "option_copy", feature = "option_default"))]
#[test]
fn test_option() {
the_same(Some(5usize));
//the_same(Some("foo bar".to_string()));
the_same(None::<usize>);
}
#[test]
fn test_fixed_size_array() {
the_same([24u32; 32]);
the_same([1u64, 2, 3, 4, 5, 6, 7, 8]);
the_same([0u8; 19]);
}
#[test]
fn test_tuple() {
the_same((1isize, ));
the_same((1isize, 2isize, 3isize));
the_same((1isize, ()));
}
#[test]
fn test_basic_struct() {
#[derive(Copy, Clone, Debug, Default, PartialEq, PeekPoke)]
struct Bar {
a: u32,
b: u32,
c: u32,
#[cfg(any(feature = "option_copy", feature = "option_default"))]
d: Option<u32>,
}
the_same(Bar {
a: 2,
b: 4,
c: 42,
#[cfg(any(feature = "option_copy", feature = "option_default"))]
d: None,
});
}
#[test]
fn test_enum() {
#[derive(Clone, Copy, Debug, PartialEq, PeekPoke)]
enum TestEnum {
NoArg,
OneArg(usize),
Args(usize, usize),
AnotherNoArg,
StructLike { x: usize, y: f32 },
}
impl Default for TestEnum {
fn default() -> Self {
TestEnum::NoArg
}
}
the_same(TestEnum::NoArg);
the_same(TestEnum::OneArg(4));
the_same(TestEnum::Args(4, 5));
the_same(TestEnum::AnotherNoArg);
the_same(TestEnum::StructLike { x: 4, y: 3.14159 });
}
#[test]
fn test_enum_cstyle() {
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PeekPoke)]
enum BorderStyle {
None = 0,
Solid = 1,
Double = 2,
Dotted = 3,
Dashed = 4,
Hidden = 5,
Groove = 6,
Ridge = 7,
Inset = 8,
Outset = 9,
}
impl Default for BorderStyle {
fn default() -> Self {
BorderStyle::None
}
}
the_same(BorderStyle::None);
the_same(BorderStyle::Solid);
the_same(BorderStyle::Double);
the_same(BorderStyle::Dotted);
the_same(BorderStyle::Dashed);
the_same(BorderStyle::Hidden);
the_same(BorderStyle::Groove);
the_same(BorderStyle::Ridge);
the_same(BorderStyle::Inset);
the_same(BorderStyle::Outset);
}
#[test]
fn test_phantom_data() {
struct Bar;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PeekPoke)]
struct Foo {
x: u32,
y: u32,
_marker: PhantomData<Bar>,
}
the_same(Foo {
x: 19,
y: 42,
_marker: PhantomData,
});
}
#[test]
fn test_generic() {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PeekPoke)]
struct Foo<T> {
x: T,
y: T,
}
the_same(Foo { x: 19.0, y: 42.0 });
}
#[test]
fn test_generic_enum() {
#[derive(Clone, Copy, Debug, Default, PartialEq, PeekPoke)]
pub struct PropertyBindingKey<T> {
pub id: usize,
_phantom: PhantomData<T>,
}
#[derive(Clone, Copy, Debug, PartialEq, PeekPoke)]
pub enum PropertyBinding<T> {
Value(T),
Binding(PropertyBindingKey<T>, T),
}
impl<T: Default> Default for PropertyBinding<T> {
fn default() -> Self {
PropertyBinding::Value(Default::default())
}
}
}
#[cfg(all(feature = "extras", feature = "option_copy"))]
mod extra_tests {
use super::*;
use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Transform3D, Vector2D};
use std::mem::size_of;
#[test]
fn euclid_types() {
the_same(Point2D::<f32>::new(1.0, 2.0));
assert_eq!(Point2D::<f32>::max_size(), 2 * size_of::<f32>());
the_same(Rect::<f32>::new(
Point2D::<f32>::new(0.0, 0.0),
Size2D::<f32>::new(100.0, 80.0),
));
assert_eq!(Rect::<f32>::max_size(), 4 * size_of::<f32>());
the_same(SideOffsets2D::<f32>::new(0.0, 10.0, -1.0, -10.0));
assert_eq!(SideOffsets2D::<f32>::max_size(), 4 * size_of::<f32>());
the_same(Transform3D::<f32>::identity());
assert_eq!(Transform3D::<f32>::max_size(), 16 * size_of::<f32>());
the_same(Vector2D::<f32>::new(1.0, 2.0));
assert_eq!(Vector2D::<f32>::max_size(), 2 * size_of::<f32>());
}
#[test]
fn webrender_api_types() {
type PipelineSourceId = i32;
#[derive(Clone, Copy, Debug, PartialEq, PeekPoke)]
struct PipelineId(pub PipelineSourceId, pub u32);
#[derive(Clone, Copy, Debug, PartialEq, PeekPoke)]
struct ClipChainId(pub u64, pub PipelineId);
#[derive(Clone, Copy, Debug, PartialEq, PeekPoke)]
struct SpatialId(pub usize, pub PipelineId);
the_same(PipelineId(42, 2));
the_same(ClipChainId(19u64, PipelineId(42, 2)));
the_same(SpatialId(19usize, PipelineId(42, 2)));
}
}

View file

@ -1,6 +0,0 @@
reorder_imports = false
reorder_imports_in_group = true
reorder_imported_names = true
error_on_line_overflow_comments = false
max_width = 100
spaces_around_ranges = true

View file

@ -1,38 +0,0 @@
[configs]
skip-check-length = false
skip-check-licenses = false
check-alphabetical-order = false
[ignore]
# Ignored packages with duplicated versions
packages = [
"cfg-if",
"core-foundation",
"core-foundation-sys",
"core-graphics",
"gl_generator",
"gleam",
"rand_core",
# https://github.com/trimental/andrew/issues/5
"rusttype",
# https://bugzilla.mozilla.org/show_bug.cgi?id=1615148
"smallvec",
"yaml-rust",
# These are tracked in bug 1587468, see there for pending work.
"proc-macro2",
"quote",
"unicode-xid",
]
# Files that are ignored for all tidy and lint checks.
files = [
"./wrench/src/egl.rs", # Copied from glutin
]
# Many directories are currently ignored while we tidy things up
# gradually.
directories = [
# Generated and upstream code combined with our own. Could use cleanup
"./target",
"./webrender/src",
]

View file

@ -1,15 +0,0 @@
[package]
name = "swgl"
version = "0.1.0"
license = "MPL-2.0"
authors = ["The Mozilla Project Developers"]
build = "build.rs"
description = "Software OpenGL implementation for WebRender."
[build-dependencies]
cc = "1.0.46"
glsl-to-cxx = { path = "../glsl-to-cxx" }
webrender_build = { path = "../webrender_build" }
[dependencies]
gleam = "0.15"

View file

@ -1,220 +0,0 @@
# swgl
Software OpenGL implementation for WebRender
## Overview
This is a relatively simple single threaded software rasterizer designed
for use by WebRender. It will shade one quad at a time using a 4xf32 vector
with one vertex per lane. It rasterizes quads usings spans and shades that
span 4 pixels at a time.
## Building
clang-cl is required to build on Windows. This can be done by installing
the llvm binaries from https://releases.llvm.org/ and adding the installation
to the path with something like `set PATH=%PATH%;C:\Program Files\LLVM\bin`.
Then `set CC=clang-cl` and `set CXX=clang-cl`. That should be sufficient
for `cc-rs` to use `clang-cl` instead of `cl`.
## Extensions
SWGL contains a number of OpenGL and GLSL extensions designed to both ease
integration with WebRender and to help accelerate span rasterization.
GLSL extension intrinsics are generally prefixed with `swgl_` to distinguish
them from other items in the GLSL namespace.
Inside GLSL, the `SWGL` preprocessor token is defined so that usage of SWGL
extensions may be conditionally compiled.
```
void swgl_drawSpanRGBA8();
void swgl_drawSpanR8();
int swgl_SpanLength;
int swgl_StepSize;
mixed swgl_interpStep(mixed varying_input);
void swgl_stepInterp();
```
SWGL's default fragment processing calls a fragment shader's `main` function
on groups of fragments in units of `swgl_StepSize`. On return, the value of
gl_FragColor is read, packed to an appropriate pixel format, and sent to the
blend stage for output to the destination framebuffer. This can be inefficient
for some types of fragment shaders, such as those that must lookup from a
texture and immediately output it, unpacking the texels only to subsequently
repack them at cost. Also, various per-fragment conditions in the shader might
need to be repeatedly checked, even though they are actually constant over
the entire primitive.
To work around this inefficiency, SWGL allows fragments to optionally be
processed over entire spans. This can both side-step the packing inefficiency
as well as more efficiently deal with conditions that remain constant over an
entire span. SWGL also introduces various specialized intrinsics for more
efficiently dealing with certain types of primitive spans with optimal
fixed-function processing.
Inside a fragment shader, a `swgl_drawSpan` function may be defined to override
the normal fragment processing for that fragment shader. The function must then
call some form of `swgl_commit` intrinsic to actually output to the destination
framebuffer via the blend stage, as normal fragment processing does not take
place otherwise as would have happened in `main`. This function is used by the
rasterizer to process an entire span of fragments that have passed the depth
test (if applicable) and clipping, but have not yet been output to the blend
stage.
The amount of fragments within the span to be processed is designated by
`swgl_SpanLength` and is always aligned to units of `swgl_StepSize`.
The size of a group of fragments in terms of which `swgl_commit` intrinsics
process and output fragments is designated by `swgl_StepSize`. The
`swgl_commit` intrinsics will deduct accordingly from `swgl_SpanLength` in
units of `swgl_StepSize` to reflect the fragments actually processed, which
may be less than the entire span or up to the entire span.
Fragments should be output until `swgl_SpanLength` becomes zero to process the
entire span. If `swgl_drawSpan` returns while leaving any fragments unprocessed,
the remaining fragments will be processed as normal by the fragment shader's
`main` function. This can be used to conditionally handle certain fast-paths
in a fragment shader by otherwise defaulting to the `main` function if
`swgl_drawSpan` can't appropriately process some or all of the fragments.
The values of any varying inputs to the fragment shader will be set to their
values for the start of the span, but do not automatically update over the
the course of a span within a given call to `swgl_drawSpan`. The
`swgl_interpStep` intrinsic may be used to get the derivative per `swgl_StepSize`
group of fragments of a varying input so that the caller may update such
variables manually if desired or otherwise use that information for processing.
The `swgl_stepInterp` intrinsic forces all such varying inputs to advance by
a single step.
The RGBA8 version will be used when the destination framebuffer is RGBA8 format,
and the R8 version will be used when the destination framebuffer is R8. Various
other intrinsics described below may have restrictions on whether they can be
used only with a certain destination framebuffer format and are noted as such if
so.
```
void swgl_clipMask(sampler2D mask, vec2 offset, vec2 bb_origin, vec2 bb_size);
```
When called from the the vertex shader, this specifies a clip mask texture to
be used to mask the currently drawn primitive while blending is enabled. This
mask will only apply to the current primitive.
The mask must be an R8 texture that will be interpreted as alpha weighting
applied to the source pixel prior to the blend stage. It is sampled 1:1 with
nearest filtering without any applied transform. The given offset specifies
the positioning of the clip mask relative to the framebuffer's viewport.
The supplied bounding box constrains sampling of the clip mask to only fall
within the given rectangle, specified relative to the clip mask offset.
Anything falling outside this rectangle will be clipped entirely. If the
rectangle is empty, then the clip mask will be ignored.
```
void swgl_antiAlias(int edgeMask);
```
When called from the vertex shader, this enables anti-aliasing for the
currently drawn primitive while blending is enabled. This setting will only
apply to the current primitive. Anti-aliasing will be applied only to the
edges corresponding to bits supplied in the mask. For simple use-cases,
the edge mask can be set to all 1 bits to enable AA for the entire quad.
The order of the bits in the edge mask must match the winding order in which
the vertices are output in the vertex shader if processed as a quad, so that
the edge ends on that vertex. The easiest way to understand this ordering
is that for a rectangle (x0,y0,x1,y1) then the edge Nth edge bit corresponds
to the edge where Nth coordinate in the rectangle is constant.
SWGL tries to use an anti-aliasing method that is reasonably close to WR's
signed-distance field approximation. WR would normally try to discern the
2D local-space coordinates of a given destination pixel relative to the
2D local-space bounding rectangle of a primitive. It then uses the screen-
space derivative to try to determine the how many local-space units equate
to a distance of around one screen-space pixel. A distance approximation
of coverage is then used based on the distance in local-space from the
the current pixel's center, roughly at half-intensity at pixel center
and ranging to zero or full intensity within a radius of half a pixel
away from the center. To account for AAing going outside the normal geometry
boundaries of the primitive, WR has to extrude the primitive by a local-space
estimate to allow some AA to happen within the extruded region.
SWGL can ultimately do this approximation more simply and get around the
extrusion limitations by just ensuring spans encompass any pixel that is
partially covered when computing span boundaries. Further, since SWGL already
knows the slope of an edge and the coordinate of the span relative to the span
boundaries, finding the partial coverage of a given span becomes easy to do
without requiring any extra interpolants to track against local-space bounds.
Essentially, SWGL just performs anti-aliasing on the actual geometry bounds,
but when the pixels on a span's edge are determined to be partially covered
during span rasterization, it uses the same distance field method as WR on
those span boundary pixels to estimate the coverage based on edge slope.
```
void swgl_commitTextureLinearRGBA8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureLinearR8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureLinearR8ToRGBA8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureLinearColorRGBA8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureLinearColorR8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureLinearColorR8ToRGBA8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureLinearRepeatRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds);
void swgl_commitTextureLinearRepeatColorRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureNearestRGBA8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureNearestColorRGBA8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureNearestRepeatRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds);
void swgl_commitTextureNearestRepeatColorRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureRGBA8(sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitTextureColorRGBA8(sampler, vec2 uv, vec4 uv_bounds, vec4|float color);
void swgl_commitTextureRepeatRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds);
void swgl_commitTextureRepeatColorRGBA8(sampler, vec2 uv, vec2 tile_repeat, vec4 uv_repeat, vec4 uv_bounds, vec4|float color);
void swgl_commitPartialTextureLinearR8(int len, sampler, vec2 uv, vec4 uv_bounds);
void swgl_commitPartialTextureLinearInvertR8(int len, sampler, vec2 uv, vec4 uv_bounds);
```
Samples and commits an entire span of texture starting at the given uv and
within the supplied uv bounds. The color variations also accept a supplied color
that modulates the result.
The RGBA8 versions may only be used to commit within `swgl_drawSpanRGBA8`, and
the R8 versions may only be used to commit within `swgl_drawSpanR8`. The R8ToRGBA8
versions may be used to sample from an R8 source while committing to an RGBA8
framebuffer.
The Linear variations use a linear filter that bilinearly interpolates between
the four samples near the pixel. The Nearest variations use a nearest filter
that chooses the closest aliased sample to the center of the pixel. If neither
Linear nor Nearest is specified in the `swgl_commitTexture` variation name, then
it will automatically select either the Linear or Nearest variation depending
on the sampler's specified filter.
The Repeat variations require an optional repeat rect that specifies how to
scale and offset the UVs, assuming the UVs are normalized to repeat in the
range 0 to 1. For NearestRepeat variations, it is assumed the repeat rect is
always within the bounds. The tile repeat limit, if non-zero, specifies the
maximum number of repetitions allowed.
The Partial variations allow committing only a sub-span rather the entire
remaining span. These are currently only implemented in linear R8 variants
for optimizing clip shaders in WebRender. The Invert variant of these is
useful for implementing clip-out modes by inverting the source texture value.
```
// Premultiplied alpha over blend, but with source color set to source alpha modulated with a constant color.
void swgl_blendDropShadow(vec4 color);
// Premultiplied alpha over blend, but treats the source as a subpixel mask modulated with a constant color.
void swgl_blendSubpixelText(vec4 color);
```
SWGL allows overriding the blend mode per-primitive by calling `swgl_blend`
intrinsics in the vertex shader. The existing blend mode set by the GL is
replaced with the one specified by the intrinsic for the current primitive.
The blend mode will be reset to the blend mode set by the GL for the next
primitive after the current one, even within the same draw call.

View file

@ -1,205 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
extern crate cc;
extern crate glsl_to_cxx;
extern crate webrender_build;
use std::collections::HashSet;
use std::fmt::Write;
use webrender_build::shader::{ShaderFeatureFlags, get_shader_features};
// Shader key is in "name feature,feature" format.
// File name needs to be formatted as "name_feature_feature".
fn shader_file(shader_key: &str) -> String {
shader_key.replace(' ', "_").replace(',', "_")
}
fn write_load_shader(shader_keys: &[String]) {
let mut load_shader = String::new();
for s in shader_keys {
let _ = write!(load_shader, "#include \"{}.h\"\n", shader_file(s));
}
load_shader.push_str("ProgramLoader load_shader(const char* name) {\n");
for s in shader_keys {
let _ = write!(load_shader, " if (!strcmp(name, \"{}\")) {{ return {}_program::loader; }}\n",
s, shader_file(s));
}
load_shader.push_str(" return nullptr;\n}\n");
std::fs::write(std::env::var("OUT_DIR").unwrap() + "/load_shader.h", load_shader).unwrap();
}
fn process_imports(shader_dir: &str, shader: &str, included: &mut HashSet<String>, output: &mut String) {
if !included.insert(shader.into()) {
return;
}
println!("cargo:rerun-if-changed={}/{}.glsl", shader_dir, shader);
let source = std::fs::read_to_string(format!("{}/{}.glsl", shader_dir, shader)).unwrap();
for line in source.lines() {
if line.starts_with("#include ") {
let imports = line["#include ".len() ..].split(',');
for import in imports {
process_imports(shader_dir, import, included, output);
}
} else if line.starts_with("#version ") || line.starts_with("#extension ") {
// ignore
} else {
output.push_str(line);
output.push('\n');
}
}
}
fn translate_shader(shader_key: &str, shader_dir: &str) {
let mut imported = String::from("#define SWGL 1\n#define __VERSION__ 150\n");
let _ = write!(imported, "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}U\n",
webrender_build::MAX_VERTEX_TEXTURE_WIDTH);
let (basename, features) =
shader_key.split_at(shader_key.find(' ').unwrap_or(shader_key.len()));
if !features.is_empty() {
for feature in features.trim().split(',') {
let _ = write!(imported, "#define WR_FEATURE_{}\n", feature);
}
}
process_imports(shader_dir, basename, &mut HashSet::new(), &mut imported);
let shader = shader_file(shader_key);
let out_dir = std::env::var("OUT_DIR").unwrap();
let imp_name = format!("{}/{}.c", out_dir, shader);
std::fs::write(&imp_name, imported).unwrap();
let mut build = cc::Build::new();
build.no_default_flags(true);
if let Ok(tool) = build.try_get_compiler() {
if tool.is_like_msvc() {
build.flag("/EP");
if tool.path().to_str().map_or(false, |p| p.contains("clang")) {
build.flag("/clang:-undef");
} else {
build.flag("/u");
}
} else {
build.flag("-xc").flag("-P").flag("-undef");
}
}
// Use SWGLPP target to avoid pulling CFLAGS/CXXFLAGS.
build.target("SWGLPP");
build.file(&imp_name);
let vs = build.clone()
.define("WR_VERTEX_SHADER", Some("1"))
.expand();
let fs = build.clone()
.define("WR_FRAGMENT_SHADER", Some("1"))
.expand();
let vs_name = format!("{}/{}.vert", out_dir, shader);
let fs_name = format!("{}/{}.frag", out_dir, shader);
std::fs::write(&vs_name, vs).unwrap();
std::fs::write(&fs_name, fs).unwrap();
let args = vec![
"glsl_to_cxx".to_string(),
vs_name,
fs_name,
];
let result = glsl_to_cxx::translate(&mut args.into_iter());
std::fs::write(format!("{}/{}.h", out_dir, shader), result).unwrap();
}
fn main() {
let shader_dir = match std::env::var("MOZ_SRC") {
Ok(dir) => dir + "/gfx/wr/webrender/res",
Err(_) => std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/../webrender/res",
};
let shader_flags =
ShaderFeatureFlags::GL |
ShaderFeatureFlags::DUAL_SOURCE_BLENDING |
ShaderFeatureFlags::ADVANCED_BLEND_EQUATION |
ShaderFeatureFlags::DEBUG;
let mut shaders: Vec<String> = Vec::new();
for (name, features) in get_shader_features(shader_flags) {
shaders.extend(features.iter().map(|f| {
if f.is_empty() { name.to_owned() } else { format!("{} {}", name, f) }
}));
}
shaders.sort();
// We need to ensure that the C preprocessor does not pull compiler flags from
// the host or target environment. Set up a SWGLPP target with empty flags to
// work around this.
if let Ok(target) = std::env::var("TARGET") {
if let Ok(cc) = std::env::var(format!("CC_{}", target))
.or(std::env::var(format!("CC_{}", target.replace("-", "_")))) {
std::env::set_var("CC_SWGLPP", cc);
}
}
std::env::set_var("CFLAGS_SWGLPP", "");
for shader in &shaders {
translate_shader(shader, &shader_dir);
}
write_load_shader(&shaders);
println!("cargo:rerun-if-changed=src/blend.h");
println!("cargo:rerun-if-changed=src/composite.h");
println!("cargo:rerun-if-changed=src/gl_defs.h");
println!("cargo:rerun-if-changed=src/glsl.h");
println!("cargo:rerun-if-changed=src/program.h");
println!("cargo:rerun-if-changed=src/rasterize.h");
println!("cargo:rerun-if-changed=src/swgl_ext.h");
println!("cargo:rerun-if-changed=src/texture.h");
println!("cargo:rerun-if-changed=src/vector_type.h");
println!("cargo:rerun-if-changed=src/gl.cc");
let mut build = cc::Build::new();
build.cpp(true);
if let Ok(tool) = build.try_get_compiler() {
if tool.is_like_msvc() {
build.flag("/std:c++17")
.flag("/EHs-")
.flag("/GR-")
.flag("/UMOZILLA_CONFIG_H");
} else {
build.flag("-std=c++17")
.flag("-fno-exceptions")
.flag("-fno-rtti")
.flag("-fno-math-errno")
.flag("-UMOZILLA_CONFIG_H");
}
// SWGL relies heavily on inlining for performance so override -Oz with -O2
if tool.args().contains(&"-Oz".into()) {
build.flag("-O2");
}
// Most GLSL compilers assume something like fast-math so we turn it on.
// However, reciprocal division makes it so 1/1 = 0.999994 which can produce a lot of fuzz
// in reftests and the use of reciprocal instructions usually involves a refinement step
// which bloats our already bloated code. Further, our shader code is sufficiently parallel
// that we're more likely to be throughput bound vs latency bound. Having fewer
// instructions makes things easier on the processor and in places where it matters we can
// probably explicitly use reciprocal instructions and avoid the refinement step.
if tool.is_like_msvc() {
build.flag("/fp:fast")
.flag("-Xclang")
.flag("-mrecip=none");
} else if tool.is_like_clang() {
// gcc only supports -mrecip=none on some targets so to keep
// things simple we don't use -ffast-math with gcc at all
build.flag("-ffast-math")
.flag("-mrecip=none");
}
}
build.file("src/gl.cc")
.define("_GLIBCXX_USE_CXX11_ABI", Some("0"))
.include(shader_dir)
.include("src")
.include(std::env::var("OUT_DIR").unwrap())
.compile("gl_cc");
}

View file

@ -1,864 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
static ALWAYS_INLINE HalfRGBA8 packRGBA8(I32 a, I32 b) {
#if USE_SSE2
return _mm_packs_epi32(a, b);
#elif USE_NEON
return vcombine_u16(vqmovun_s32(a), vqmovun_s32(b));
#else
return CONVERT(combine(a, b), HalfRGBA8);
#endif
}
static ALWAYS_INLINE WideRGBA8 pack_pixels_RGBA8(const vec4& v,
float scale = 255.0f) {
ivec4 i = round_pixel(v, scale);
HalfRGBA8 xz = packRGBA8(i.z, i.x);
HalfRGBA8 yw = packRGBA8(i.y, i.w);
HalfRGBA8 xyzwl = zipLow(xz, yw);
HalfRGBA8 xyzwh = zipHigh(xz, yw);
HalfRGBA8 lo = zip2Low(xyzwl, xyzwh);
HalfRGBA8 hi = zip2High(xyzwl, xyzwh);
return combine(lo, hi);
}
static ALWAYS_INLINE WideRGBA8 pack_pixels_RGBA8(Float alpha,
float scale = 255.0f) {
I32 i = round_pixel(alpha, scale);
HalfRGBA8 c = packRGBA8(i, i);
c = zipLow(c, c);
return zip(c, c);
}
static ALWAYS_INLINE WideRGBA8 pack_pixels_RGBA8(float alpha,
float scale = 255.0f) {
I32 i = round_pixel(alpha, scale);
return repeat2(packRGBA8(i, i));
}
UNUSED static ALWAYS_INLINE WideRGBA8 pack_pixels_RGBA8(const vec4_scalar& v,
float scale = 255.0f) {
I32 i = round_pixel((Float){v.z, v.y, v.x, v.w}, scale);
return repeat2(packRGBA8(i, i));
}
static ALWAYS_INLINE WideRGBA8 pack_pixels_RGBA8() {
return pack_pixels_RGBA8(fragment_shader->gl_FragColor);
}
static ALWAYS_INLINE WideRGBA8 pack_pixels_RGBA8(WideRGBA32F v,
float scale = 255.0f) {
ivec4 i = round_pixel(bit_cast<vec4>(v), scale);
return combine(packRGBA8(i.x, i.y), packRGBA8(i.z, i.w));
}
static ALWAYS_INLINE WideR8 packR8(I32 a) {
#if USE_SSE2
return lowHalf(bit_cast<V8<uint16_t>>(_mm_packs_epi32(a, a)));
#elif USE_NEON
return vqmovun_s32(a);
#else
return CONVERT(a, WideR8);
#endif
}
static ALWAYS_INLINE WideR8 pack_pixels_R8(Float c, float scale = 255.0f) {
return packR8(round_pixel(c, scale));
}
static ALWAYS_INLINE WideR8 pack_pixels_R8() {
return pack_pixels_R8(fragment_shader->gl_FragColor.x);
}
// Load a partial span > 0 and < 4 pixels.
template <typename V, typename P>
static ALWAYS_INLINE V partial_load_span(const P* src, int span) {
return bit_cast<V>(
(span >= 2
? combine(unaligned_load<V2<P>>(src),
V2<P>{span > 2 ? unaligned_load<P>(src + 2) : P(0), 0})
: V4<P>{unaligned_load<P>(src), 0, 0, 0}));
}
// Store a partial span > 0 and < 4 pixels.
template <typename V, typename P>
static ALWAYS_INLINE void partial_store_span(P* dst, V src, int span) {
auto pixels = bit_cast<V4<P>>(src);
if (span >= 2) {
unaligned_store(dst, lowHalf(pixels));
if (span > 2) {
unaligned_store(dst + 2, pixels.z);
}
} else {
unaligned_store(dst, pixels.x);
}
}
// Dispatcher that chooses when to load a full or partial span
template <typename V, typename P>
static ALWAYS_INLINE V load_span(const P* src, int span) {
if (span >= 4) {
return unaligned_load<V, P>(src);
} else {
return partial_load_span<V, P>(src, span);
}
}
// Dispatcher that chooses when to store a full or partial span
template <typename V, typename P>
static ALWAYS_INLINE void store_span(P* dst, V src, int span) {
if (span >= 4) {
unaligned_store<V, P>(dst, src);
} else {
partial_store_span<V, P>(dst, src, span);
}
}
template <typename T>
static ALWAYS_INLINE T muldiv256(T x, T y) {
return (x * y) >> 8;
}
// (x*y + x) >> 8, cheap approximation of (x*y) / 255
template <typename T>
static ALWAYS_INLINE T muldiv255(T x, T y) {
return (x * y + x) >> 8;
}
template <typename V>
static ALWAYS_INLINE WideRGBA8 pack_span(uint32_t*, const V& v,
float scale = 255.0f) {
return pack_pixels_RGBA8(v, scale);
}
template <typename C>
static ALWAYS_INLINE WideR8 pack_span(uint8_t*, C c, float scale = 255.0f) {
return pack_pixels_R8(c, scale);
}
// Helper functions to apply a color modulus when available.
struct NoColor {};
template <typename P>
static ALWAYS_INLINE P applyColor(P src, NoColor) {
return src;
}
struct InvertColor {};
template <typename P>
static ALWAYS_INLINE P applyColor(P src, InvertColor) {
return 255 - src;
}
template <typename P>
static ALWAYS_INLINE P applyColor(P src, P color) {
return muldiv255(color, src);
}
static ALWAYS_INLINE WideRGBA8 applyColor(PackedRGBA8 src, WideRGBA8 color) {
return applyColor(unpack(src), color);
}
template <typename P, typename C>
static ALWAYS_INLINE auto packColor(P* buf, C color) {
return pack_span(buf, color, 255.0f);
}
template <typename P>
static ALWAYS_INLINE NoColor packColor(UNUSED P* buf, NoColor noColor) {
return noColor;
}
template <typename P>
static ALWAYS_INLINE InvertColor packColor(UNUSED P* buf,
InvertColor invertColor) {
return invertColor;
}
// Single argument variation that takes an explicit destination buffer type.
template <typename P, typename C>
static ALWAYS_INLINE auto packColor(C color) {
// Just pass in a typed null pointer, as the pack routines never use the
// pointer's value, just its type.
return packColor((P*)0, color);
}
// Byte-wise addition for when x or y is a signed 8-bit value stored in the
// low byte of a larger type T only with zeroed-out high bits, where T is
// greater than 8 bits, i.e. uint16_t. This can result when muldiv255 is used
// upon signed operands, using up all the precision in a 16 bit integer, and
// potentially losing the sign bit in the last >> 8 shift. Due to the
// properties of two's complement arithmetic, even though we've discarded the
// sign bit, we can still represent a negative number under addition (without
// requiring any extra sign bits), just that any negative number will behave
// like a large unsigned number under addition, generating a single carry bit
// on overflow that we need to discard. Thus, just doing a byte-wise add will
// overflow without the troublesome carry, giving us only the remaining 8 low
// bits we actually need while keeping the high bits at zero.
template <typename T>
static ALWAYS_INLINE T addlow(T x, T y) {
typedef VectorType<uint8_t, sizeof(T)> bytes;
return bit_cast<T>(bit_cast<bytes>(x) + bit_cast<bytes>(y));
}
// Replace color components of each pixel with the pixel's alpha values.
template <typename T>
static ALWAYS_INLINE T alphas(T c) {
return SHUFFLE(c, c, 3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15);
}
// Replace the alpha values of the first vector with alpha values from the
// second, while leaving the color components unmodified.
template <typename T>
static ALWAYS_INLINE T set_alphas(T c, T a) {
return SHUFFLE(c, a, 0, 1, 2, 19, 4, 5, 6, 23, 8, 9, 10, 27, 12, 13, 14, 31);
}
// Miscellaneous helper functions for working with packed RGBA8 data.
static ALWAYS_INLINE HalfRGBA8 if_then_else(V8<int16_t> c, HalfRGBA8 t,
HalfRGBA8 e) {
return bit_cast<HalfRGBA8>((c & t) | (~c & e));
}
template <typename T, typename C, int N>
static ALWAYS_INLINE VectorType<T, N> if_then_else(VectorType<C, N> c,
VectorType<T, N> t,
VectorType<T, N> e) {
return combine(if_then_else(lowHalf(c), lowHalf(t), lowHalf(e)),
if_then_else(highHalf(c), highHalf(t), highHalf(e)));
}
static ALWAYS_INLINE HalfRGBA8 min(HalfRGBA8 x, HalfRGBA8 y) {
#if USE_SSE2
return bit_cast<HalfRGBA8>(
_mm_min_epi16(bit_cast<V8<int16_t>>(x), bit_cast<V8<int16_t>>(y)));
#elif USE_NEON
return vminq_u16(x, y);
#else
return if_then_else(x < y, x, y);
#endif
}
template <typename T, int N>
static ALWAYS_INLINE VectorType<T, N> min(VectorType<T, N> x,
VectorType<T, N> y) {
return combine(min(lowHalf(x), lowHalf(y)), min(highHalf(x), highHalf(y)));
}
static ALWAYS_INLINE HalfRGBA8 max(HalfRGBA8 x, HalfRGBA8 y) {
#if USE_SSE2
return bit_cast<HalfRGBA8>(
_mm_max_epi16(bit_cast<V8<int16_t>>(x), bit_cast<V8<int16_t>>(y)));
#elif USE_NEON
return vmaxq_u16(x, y);
#else
return if_then_else(x > y, x, y);
#endif
}
template <typename T, int N>
static ALWAYS_INLINE VectorType<T, N> max(VectorType<T, N> x,
VectorType<T, N> y) {
return combine(max(lowHalf(x), lowHalf(y)), max(highHalf(x), highHalf(y)));
}
template <typename T, int N>
static ALWAYS_INLINE VectorType<T, N> recip(VectorType<T, N> v) {
return combine(recip(lowHalf(v)), recip(highHalf(v)));
}
// Helper to get the reciprocal if the value is non-zero, or otherwise default
// to the supplied fallback value.
template <typename V>
static ALWAYS_INLINE V recip_or(V v, float f) {
return if_then_else(v != V(0.0f), recip(v), V(f));
}
template <typename T, int N>
static ALWAYS_INLINE VectorType<T, N> inversesqrt(VectorType<T, N> v) {
return combine(inversesqrt(lowHalf(v)), inversesqrt(highHalf(v)));
}
// Extract the alpha components so that we can cheaply calculate the reciprocal
// on a single SIMD register. Then multiply the duplicated alpha reciprocal with
// the pixel data. 0 alpha is treated as transparent black.
static ALWAYS_INLINE WideRGBA32F unpremultiply(WideRGBA32F v) {
Float a = recip_or((Float){v[3], v[7], v[11], v[15]}, 0.0f);
return v * a.xxxxyyyyzzzzwwww;
}
// Packed RGBA32F data is AoS in BGRA order. Transpose it to SoA and swizzle to
// RGBA to unpack.
static ALWAYS_INLINE vec4 unpack(PackedRGBA32F c) {
return bit_cast<vec4>(
SHUFFLE(c, c, 2, 6, 10, 14, 1, 5, 9, 13, 0, 4, 8, 12, 3, 7, 11, 15));
}
// The following lum/sat functions mostly follow the KHR_blend_equation_advanced
// specification but are rearranged to work on premultiplied data.
static ALWAYS_INLINE Float lumv3(vec3 v) {
return v.x * 0.30f + v.y * 0.59f + v.z * 0.11f;
}
static ALWAYS_INLINE Float minv3(vec3 v) { return min(min(v.x, v.y), v.z); }
static ALWAYS_INLINE Float maxv3(vec3 v) { return max(max(v.x, v.y), v.z); }
static inline vec3 clip_color(vec3 v, Float lum, Float alpha) {
Float mincol = max(-minv3(v), lum);
Float maxcol = max(maxv3(v), alpha - lum);
return lum + v * (lum * (alpha - lum) * recip_or(mincol * maxcol, 0.0f));
}
static inline vec3 set_lum(vec3 base, vec3 ref, Float alpha) {
return clip_color(base - lumv3(base), lumv3(ref), alpha);
}
static inline vec3 set_lum_sat(vec3 base, vec3 sref, vec3 lref, Float alpha) {
vec3 diff = base - minv3(base);
Float sbase = maxv3(diff);
Float ssat = maxv3(sref) - minv3(sref);
// The sbase range is rescaled to ssat. If sbase has 0 extent, then rescale
// to black, as per specification.
return set_lum(diff * ssat * recip_or(sbase, 0.0f), lref, alpha);
}
// Flags the reflect the current blend-stage clipping to be applied.
enum SWGLClipFlag {
SWGL_CLIP_FLAG_MASK = 1 << 0,
SWGL_CLIP_FLAG_AA = 1 << 1,
SWGL_CLIP_FLAG_BLEND_OVERRIDE = 1 << 2,
};
static int swgl_ClipFlags = 0;
static BlendKey swgl_BlendOverride = BLEND_KEY_NONE;
static WideRGBA8 swgl_BlendColorRGBA8 = {0};
static WideRGBA8 swgl_BlendAlphaRGBA8 = {0};
// A pointer into the color buffer for the start of the span.
static void* swgl_SpanBuf = nullptr;
// A pointer into the clip mask for the start of the span.
static uint8_t* swgl_ClipMaskBuf = nullptr;
static ALWAYS_INLINE WideR8 expand_mask(UNUSED uint8_t* buf, WideR8 mask) {
return mask;
}
static ALWAYS_INLINE WideRGBA8 expand_mask(UNUSED uint32_t* buf, WideR8 mask) {
WideRG8 maskRG = zip(mask, mask);
return zip(maskRG, maskRG);
}
// Loads a chunk of clip masks. The current pointer into the color buffer is
// used to reconstruct the relative position within the span. From there, the
// pointer into the clip mask can be generated from the start of the clip mask
// span.
template <typename P>
static ALWAYS_INLINE uint8_t* get_clip_mask(P* buf) {
return &swgl_ClipMaskBuf[buf - (P*)swgl_SpanBuf];
}
template <typename P>
static ALWAYS_INLINE auto load_clip_mask(P* buf, int span)
-> decltype(expand_mask(buf, 0)) {
return expand_mask(buf,
unpack(load_span<PackedR8>(get_clip_mask(buf), span)));
}
// Temporarily removes masking from the blend stage, assuming the caller will
// handle it.
static ALWAYS_INLINE void override_clip_mask() {
blend_key = BlendKey(blend_key - MASK_BLEND_KEY_NONE);
}
// Restores masking to the blend stage, assuming it was previously overridden.
static ALWAYS_INLINE void restore_clip_mask() {
blend_key = BlendKey(MASK_BLEND_KEY_NONE + blend_key);
}
// A pointer to the start of the opaque destination region of the span for AA.
static const uint8_t* swgl_OpaqueStart = nullptr;
// The size, in bytes, of the opaque region.
static uint32_t swgl_OpaqueSize = 0;
// AA coverage distance offsets for the left and right edges.
static Float swgl_LeftAADist = 0.0f;
static Float swgl_RightAADist = 0.0f;
// AA coverage slope values used for accumulating coverage for each step.
static Float swgl_AASlope = 0.0f;
// Get the amount of pixels we need to process before the start of the opaque
// region.
template <typename P>
static ALWAYS_INLINE int get_aa_opaque_start(P* buf) {
return max(int((P*)swgl_OpaqueStart - buf), 0);
}
// Assuming we are already in the opaque part of the span, return the remaining
// size of the opaque part.
template <typename P>
static ALWAYS_INLINE int get_aa_opaque_size(P* buf) {
return max(int((P*)&swgl_OpaqueStart[swgl_OpaqueSize] - buf), 0);
}
// Temporarily removes anti-aliasing from the blend stage, assuming the caller
// will handle it.
static ALWAYS_INLINE void override_aa() {
blend_key = BlendKey(blend_key - AA_BLEND_KEY_NONE);
}
// Restores anti-aliasing to the blend stage, assuming it was previously
// overridden.
static ALWAYS_INLINE void restore_aa() {
blend_key = BlendKey(AA_BLEND_KEY_NONE + blend_key);
}
static PREFER_INLINE WideRGBA8 blend_pixels(uint32_t* buf, PackedRGBA8 pdst,
WideRGBA8 src, int span = 4) {
WideRGBA8 dst = unpack(pdst);
const WideRGBA8 RGB_MASK = {0xFFFF, 0xFFFF, 0xFFFF, 0, 0xFFFF, 0xFFFF,
0xFFFF, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0,
0xFFFF, 0xFFFF, 0xFFFF, 0};
const WideRGBA8 ALPHA_MASK = {0, 0, 0, 0xFFFF, 0, 0, 0, 0xFFFF,
0, 0, 0, 0xFFFF, 0, 0, 0, 0xFFFF};
const WideRGBA8 ALPHA_OPAQUE = {0, 0, 0, 255, 0, 0, 0, 255,
0, 0, 0, 255, 0, 0, 0, 255};
// clang-format off
// Computes AA for the given pixel based on the offset of the pixel within
// destination row. Given the initial coverage offsets for the left and right
// edges, the offset is scaled by the slope and accumulated to find the
// minimum coverage value for the pixel. A final weight is generated that
// can be used to scale the source pixel.
#define DO_AA(format, body) \
do { \
int offset = int((const uint8_t*)buf - swgl_OpaqueStart); \
if (uint32_t(offset) >= swgl_OpaqueSize) { \
Float delta = swgl_AASlope * float(offset); \
Float dist = clamp(min(swgl_LeftAADist + delta.x, \
swgl_RightAADist + delta.y), \
0.0f, 256.0f); \
auto aa = pack_pixels_##format(dist, 1.0f); \
body; \
} \
} while (0)
// Each blend case is preceded by the MASK_ variant. The MASK_ case first
// loads the mask values and multiplies the source value by them. After, it
// falls through to the normal blending case using the masked source. The
// AA_ variations may further precede the blend cases, in which case the
// source value is further modified before use.
#define BLEND_CASE_KEY(key) \
case AA_##key: \
DO_AA(RGBA8, src = muldiv256(src, aa)); \
goto key; \
case AA_MASK_##key: \
DO_AA(RGBA8, src = muldiv256(src, aa)); \
FALLTHROUGH; \
case MASK_##key: \
src = muldiv255(src, load_clip_mask(buf, span)); \
FALLTHROUGH; \
case key: key
#define BLEND_CASE(...) BLEND_CASE_KEY(BLEND_KEY(__VA_ARGS__))
switch (blend_key) {
BLEND_CASE(GL_ONE, GL_ZERO):
return src;
BLEND_CASE(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,
GL_ONE_MINUS_SRC_ALPHA):
// dst + src.a*(src.rgb1 - dst)
// use addlow for signed overflow
return addlow(dst, muldiv255(alphas(src), (src | ALPHA_OPAQUE) - dst));
BLEND_CASE(GL_ONE, GL_ONE_MINUS_SRC_ALPHA):
return src + dst - muldiv255(dst, alphas(src));
BLEND_CASE(GL_ZERO, GL_ONE_MINUS_SRC_COLOR):
return dst - muldiv255(dst, src);
BLEND_CASE(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE):
return dst - (muldiv255(dst, src) & RGB_MASK);
BLEND_CASE(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA):
return dst - muldiv255(dst, alphas(src));
BLEND_CASE(GL_ZERO, GL_SRC_COLOR):
return muldiv255(src, dst);
BLEND_CASE(GL_ONE, GL_ONE):
return src + dst;
BLEND_CASE(GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA):
return src + dst - (muldiv255(dst, src) & ALPHA_MASK);
BLEND_CASE(GL_ONE_MINUS_DST_ALPHA, GL_ONE, GL_ZERO, GL_ONE):
// src*(1-dst.a) + dst*1 = src - src*dst.a + dst
return dst + ((src - muldiv255(src, alphas(dst))) & RGB_MASK);
BLEND_CASE(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR):
// src*k + (1-src)*dst = src*k + dst -
// src*dst = dst + src*(k - dst) use addlow
// for signed overflow
return addlow(
dst, muldiv255(src, repeat2(ctx->blendcolor) - dst));
// We must explicitly handle the masked/anti-aliased secondary blend case.
// The secondary color as well as the source must be multiplied by the
// weights.
case BLEND_KEY(GL_ONE, GL_ONE_MINUS_SRC1_COLOR): {
WideRGBA8 secondary =
applyColor(dst,
packColor<uint32_t>(fragment_shader->gl_SecondaryFragColor));
return src + dst - secondary;
}
case MASK_BLEND_KEY(GL_ONE, GL_ONE_MINUS_SRC1_COLOR): {
WideRGBA8 secondary =
applyColor(dst,
packColor<uint32_t>(fragment_shader->gl_SecondaryFragColor));
WideRGBA8 mask = load_clip_mask(buf, span);
return muldiv255(src, mask) + dst - muldiv255(secondary, mask);
}
case AA_BLEND_KEY(GL_ONE, GL_ONE_MINUS_SRC1_COLOR): {
WideRGBA8 secondary =
applyColor(dst,
packColor<uint32_t>(fragment_shader->gl_SecondaryFragColor));
DO_AA(RGBA8, {
src = muldiv256(src, aa);
secondary = muldiv256(secondary, aa);
});
return src + dst - secondary;
}
case AA_MASK_BLEND_KEY(GL_ONE, GL_ONE_MINUS_SRC1_COLOR): {
WideRGBA8 secondary =
applyColor(dst,
packColor<uint32_t>(fragment_shader->gl_SecondaryFragColor));
WideRGBA8 mask = load_clip_mask(buf, span);
DO_AA(RGBA8, mask = muldiv256(mask, aa));
return muldiv255(src, mask) + dst - muldiv255(secondary, mask);
}
BLEND_CASE(GL_MIN):
return min(src, dst);
BLEND_CASE(GL_MAX):
return max(src, dst);
// The KHR_blend_equation_advanced spec describes the blend equations such
// that the unpremultiplied values Cs, Cd, As, Ad and function f combine to
// the result:
// Cr = f(Cs,Cd)*As*Ad + Cs*As*(1-Ad) + Cd*AD*(1-As)
// Ar = As*Ad + As*(1-Ad) + Ad*(1-As)
// However, working with unpremultiplied values requires expensive math to
// unpremultiply and premultiply again during blending. We can use the fact
// that premultiplied value P = C*A and simplify the equations such that no
// unpremultiplied colors are necessary, allowing us to stay with integer
// math that avoids floating-point conversions in the common case. Some of
// the blend modes require division or sqrt, in which case we do convert
// to (possibly transposed/unpacked) floating-point to implement the mode.
// However, most common modes can still use cheaper premultiplied integer
// math. As an example, the multiply mode f(Cs,Cd) = Cs*Cd is simplified
// to:
// Cr = Cs*Cd*As*Ad + Cs*As*(1-Ad) + Cd*Ad*(1-As)
// .. Pr = Ps*Pd + Ps - Ps*Ad + Pd - Pd*As
// Ar = As*Ad + As - As*Ad + Ad - Ad*As
// .. Ar = As + Ad - As*Ad
// Note that the alpha equation is the same for all blend equations, such
// that so long as the implementation results in As + Ad - As*Ad, we can
// avoid using separate instructions to compute the alpha result, which is
// dependent on the math used to implement each blend mode. The exact
// reductions used to get the final math for every blend mode are too
// involved to show here in comments, but mostly follows from replacing
// Cs*As and Cd*Ad with Ps and Ps while factoring out as many common terms
// as possible.
BLEND_CASE(GL_MULTIPLY_KHR): {
WideRGBA8 diff = muldiv255(alphas(src) - (src & RGB_MASK),
alphas(dst) - (dst & RGB_MASK));
return src + dst + (diff & RGB_MASK) - alphas(diff);
}
BLEND_CASE(GL_SCREEN_KHR):
return src + dst - muldiv255(src, dst);
BLEND_CASE(GL_OVERLAY_KHR): {
WideRGBA8 srcA = alphas(src);
WideRGBA8 dstA = alphas(dst);
WideRGBA8 diff = muldiv255(src, dst) + muldiv255(srcA - src, dstA - dst);
return src + dst +
if_then_else(dst * 2 <= dstA, (diff & RGB_MASK) - alphas(diff),
-diff);
}
BLEND_CASE(GL_DARKEN_KHR):
return src + dst -
max(muldiv255(src, alphas(dst)), muldiv255(dst, alphas(src)));
BLEND_CASE(GL_LIGHTEN_KHR):
return src + dst -
min(muldiv255(src, alphas(dst)), muldiv255(dst, alphas(src)));
BLEND_CASE(GL_COLORDODGE_KHR): {
// Color-dodge and color-burn require division, so we convert to FP math
// here, but avoid transposing to a vec4.
WideRGBA32F srcF = CONVERT(src, WideRGBA32F);
WideRGBA32F srcA = alphas(srcF);
WideRGBA32F dstF = CONVERT(dst, WideRGBA32F);
WideRGBA32F dstA = alphas(dstF);
return pack_pixels_RGBA8(
srcA * set_alphas(
min(dstA, dstF * srcA * recip_or(srcA - srcF, 255.0f)),
dstF) +
srcF * (255.0f - dstA) + dstF * (255.0f - srcA),
1.0f / 255.0f);
}
BLEND_CASE(GL_COLORBURN_KHR): {
WideRGBA32F srcF = CONVERT(src, WideRGBA32F);
WideRGBA32F srcA = alphas(srcF);
WideRGBA32F dstF = CONVERT(dst, WideRGBA32F);
WideRGBA32F dstA = alphas(dstF);
return pack_pixels_RGBA8(
srcA * set_alphas((dstA - min(dstA, (dstA - dstF) * srcA *
recip_or(srcF, 255.0f))),
dstF) +
srcF * (255.0f - dstA) + dstF * (255.0f - srcA),
1.0f / 255.0f);
}
BLEND_CASE(GL_HARDLIGHT_KHR): {
WideRGBA8 srcA = alphas(src);
WideRGBA8 dstA = alphas(dst);
WideRGBA8 diff = muldiv255(src, dst) + muldiv255(srcA - src, dstA - dst);
return src + dst +
if_then_else(src * 2 <= srcA, (diff & RGB_MASK) - alphas(diff),
-diff);
}
BLEND_CASE(GL_SOFTLIGHT_KHR): {
// Soft-light requires an unpremultiply that can't be factored out as
// well as a sqrt, so we convert to FP math here, but avoid transposing
// to a vec4.
WideRGBA32F srcF = CONVERT(src, WideRGBA32F);
WideRGBA32F srcA = alphas(srcF);
WideRGBA32F dstF = CONVERT(dst, WideRGBA32F);
WideRGBA32F dstA = alphas(dstF);
WideRGBA32F dstU = unpremultiply(dstF);
WideRGBA32F scale = srcF + srcF - srcA;
return pack_pixels_RGBA8(
dstF * (255.0f +
set_alphas(
scale *
if_then_else(scale < 0.0f, 1.0f - dstU,
min((16.0f * dstU - 12.0f) * dstU + 3.0f,
inversesqrt(dstU) - 1.0f)),
WideRGBA32F(0.0f))) +
srcF * (255.0f - dstA),
1.0f / 255.0f);
}
BLEND_CASE(GL_DIFFERENCE_KHR): {
WideRGBA8 diff =
min(muldiv255(dst, alphas(src)), muldiv255(src, alphas(dst)));
return src + dst - diff - (diff & RGB_MASK);
}
BLEND_CASE(GL_EXCLUSION_KHR): {
WideRGBA8 diff = muldiv255(src, dst);
return src + dst - diff - (diff & RGB_MASK);
}
// The HSL blend modes are non-separable and require complicated use of
// division. It is advantageous to convert to FP and transpose to vec4
// math to more easily manipulate the individual color components.
#define DO_HSL(rgb) \
do { \
vec4 srcV = unpack(CONVERT(src, PackedRGBA32F)); \
vec4 dstV = unpack(CONVERT(dst, PackedRGBA32F)); \
Float srcA = srcV.w * (1.0f / 255.0f); \
Float dstA = dstV.w * (1.0f / 255.0f); \
Float srcDstA = srcV.w * dstA; \
vec3 srcC = vec3(srcV) * dstA; \
vec3 dstC = vec3(dstV) * srcA; \
return pack_pixels_RGBA8(vec4(rgb + vec3(srcV) - srcC + vec3(dstV) - dstC, \
srcV.w + dstV.w - srcDstA), \
1.0f); \
} while (0)
BLEND_CASE(GL_HSL_HUE_KHR):
DO_HSL(set_lum_sat(srcC, dstC, dstC, srcDstA));
BLEND_CASE(GL_HSL_SATURATION_KHR):
DO_HSL(set_lum_sat(dstC, srcC, dstC, srcDstA));
BLEND_CASE(GL_HSL_COLOR_KHR):
DO_HSL(set_lum(srcC, dstC, srcDstA));
BLEND_CASE(GL_HSL_LUMINOSITY_KHR):
DO_HSL(set_lum(dstC, srcC, srcDstA));
// SWGL-specific extended blend modes.
BLEND_CASE(SWGL_BLEND_DROP_SHADOW): {
// Premultiplied alpha over blend, but with source color set to source alpha
// modulated with a constant color.
WideRGBA8 color = applyColor(alphas(src), swgl_BlendColorRGBA8);
return color + dst - muldiv255(dst, alphas(color));
}
BLEND_CASE(SWGL_BLEND_SUBPIXEL_TEXT):
// Premultiplied alpha over blend, but treats the source as a subpixel mask
// modulated with a constant color.
return applyColor(src, swgl_BlendColorRGBA8) + dst -
muldiv255(dst, applyColor(src, swgl_BlendAlphaRGBA8));
default:
UNREACHABLE;
// return src;
}
#undef BLEND_CASE
#undef BLEND_CASE_KEY
// clang-format on
}
static PREFER_INLINE WideR8 blend_pixels(uint8_t* buf, WideR8 dst, WideR8 src,
int span = 4) {
// clang-format off
#define BLEND_CASE_KEY(key) \
case AA_##key: \
DO_AA(R8, src = muldiv256(src, aa)); \
goto key; \
case AA_MASK_##key: \
DO_AA(R8, src = muldiv256(src, aa)); \
FALLTHROUGH; \
case MASK_##key: \
src = muldiv255(src, load_clip_mask(buf, span)); \
FALLTHROUGH; \
case key: key
#define BLEND_CASE(...) BLEND_CASE_KEY(BLEND_KEY(__VA_ARGS__))
switch (blend_key) {
BLEND_CASE(GL_ONE, GL_ZERO):
return src;
BLEND_CASE(GL_ZERO, GL_SRC_COLOR):
return muldiv255(src, dst);
BLEND_CASE(GL_ONE, GL_ONE):
return src + dst;
default:
UNREACHABLE;
// return src;
}
#undef BLEND_CASE
#undef BLEND_CASE_KEY
// clang-format on
}
static ALWAYS_INLINE void commit_span(uint32_t* buf, WideRGBA8 r) {
unaligned_store(buf, pack(r));
}
static ALWAYS_INLINE void commit_span(uint32_t* buf, WideRGBA8 r, int len) {
partial_store_span(buf, pack(r), len);
}
static ALWAYS_INLINE WideRGBA8 blend_span(uint32_t* buf, WideRGBA8 r) {
return blend_pixels(buf, unaligned_load<PackedRGBA8>(buf), r);
}
static ALWAYS_INLINE WideRGBA8 blend_span(uint32_t* buf, WideRGBA8 r, int len) {
return blend_pixels(buf, partial_load_span<PackedRGBA8>(buf, len), r, len);
}
static ALWAYS_INLINE void commit_span(uint32_t* buf, PackedRGBA8 r) {
unaligned_store(buf, r);
}
static ALWAYS_INLINE void commit_span(uint32_t* buf, PackedRGBA8 r, int len) {
partial_store_span(buf, r, len);
}
static ALWAYS_INLINE PackedRGBA8 blend_span(uint32_t* buf, PackedRGBA8 r) {
return pack(blend_span(buf, unpack(r)));
}
static ALWAYS_INLINE PackedRGBA8 blend_span(uint32_t* buf, PackedRGBA8 r,
int len) {
return pack(blend_span(buf, unpack(r), len));
}
static ALWAYS_INLINE void commit_span(uint8_t* buf, WideR8 r) {
unaligned_store(buf, pack(r));
}
static ALWAYS_INLINE void commit_span(uint8_t* buf, WideR8 r, int len) {
partial_store_span(buf, pack(r), len);
}
static ALWAYS_INLINE WideR8 blend_span(uint8_t* buf, WideR8 r) {
return blend_pixels(buf, unpack(unaligned_load<PackedR8>(buf)), r);
}
static ALWAYS_INLINE WideR8 blend_span(uint8_t* buf, WideR8 r, int len) {
return blend_pixels(buf, unpack(partial_load_span<PackedR8>(buf, len)), r,
len);
}
static ALWAYS_INLINE void commit_span(uint8_t* buf, PackedR8 r) {
unaligned_store(buf, r);
}
static ALWAYS_INLINE void commit_span(uint8_t* buf, PackedR8 r, int len) {
partial_store_span(buf, r, len);
}
static ALWAYS_INLINE PackedR8 blend_span(uint8_t* buf, PackedR8 r) {
return pack(blend_span(buf, unpack(r)));
}
static ALWAYS_INLINE PackedR8 blend_span(uint8_t* buf, PackedR8 r, int len) {
return pack(blend_span(buf, unpack(r), len));
}
template <bool BLEND, typename P, typename R>
static ALWAYS_INLINE void commit_blend_span(P* buf, R r) {
if (BLEND) {
commit_span(buf, blend_span(buf, r));
} else {
commit_span(buf, r);
}
}
template <bool BLEND, typename P, typename R>
static ALWAYS_INLINE void commit_blend_span(P* buf, R r, int len) {
if (BLEND) {
commit_span(buf, blend_span(buf, r, len), len);
} else {
commit_span(buf, r, len);
}
}
template <typename P, typename R>
static ALWAYS_INLINE void commit_blend_solid_span(P* buf, R r, int len) {
for (P* end = &buf[len & ~3]; buf < end; buf += 4) {
commit_span(buf, blend_span(buf, r));
}
len &= 3;
if (len > 0) {
partial_store_span(buf, pack(blend_span(buf, r, len)), len);
}
}
template <bool BLEND>
static void commit_solid_span(uint32_t* buf, WideRGBA8 r, int len) {
commit_blend_solid_span(buf, r, len);
}
template <>
ALWAYS_INLINE void commit_solid_span<false>(uint32_t* buf, WideRGBA8 r,
int len) {
fill_n(buf, len, bit_cast<U32>(pack(r)).x);
}
template <bool BLEND>
static void commit_solid_span(uint8_t* buf, WideR8 r, int len) {
commit_blend_solid_span(buf, r, len);
}
template <>
ALWAYS_INLINE void commit_solid_span<false>(uint8_t* buf, WideR8 r, int len) {
PackedR8 p = pack(r);
if (uintptr_t(buf) & 3) {
int align = 4 - (uintptr_t(buf) & 3);
align = min(align, len);
partial_store_span(buf, p, align);
buf += align;
len -= align;
}
fill_n((uint32_t*)buf, len / 4, bit_cast<uint32_t>(p));
buf += len & ~3;
len &= 3;
if (len > 0) {
partial_store_span(buf, p, len);
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,216 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
typedef int8_t GLbyte;
typedef uint8_t GLubyte;
typedef int16_t GLshort;
typedef uint16_t GLushort;
typedef int32_t GLint;
typedef uint32_t GLuint;
typedef int64_t GLint64;
typedef uint64_t GLuint64;
typedef float GLfloat;
typedef double GLdouble;
typedef uint32_t GLenum;
typedef uint8_t GLboolean;
typedef uint32_t GLbitfield;
typedef int32_t GLsizei;
typedef size_t GLsizeiptr;
typedef intptr_t GLintptr;
#define GL_FALSE 0
#define GL_TRUE 1
#define GL_NONE 0
#define GL_NO_ERROR 0
#define GL_RGBA32F 0x8814
#define GL_RGBA8 0x8058
#define GL_R8 0x8229
#define GL_R16 0x822A
#define GL_RGBA32I 0x8D82
#define GL_BGRA8 0x93A1
#define GL_RG8 0x822B
#define GL_BYTE 0x1400
#define GL_UNSIGNED_BYTE 0x1401
#define GL_SHORT 0x1402
#define GL_UNSIGNED_SHORT 0x1403
#define GL_INT 0x1404
#define GL_UNSIGNED_INT 0x1405
#define GL_FLOAT 0x1406
#define GL_DOUBLE 0x1408
#define GL_RED 0x1903
#define GL_GREEN 0x1904
#define GL_BLUE 0x1905
#define GL_ALPHA 0x1906
#define GL_RGB 0x1907
#define GL_RGBA 0x1908
#define GL_RGBA_INTEGER 0x8D99
#define GL_BGRA 0x80E1
#define GL_RG 0x8227
#define GL_DEPTH_COMPONENT 0x1902
#define GL_DEPTH_COMPONENT16 0x81A5
#define GL_DEPTH_COMPONENT24 0x81A6
#define GL_DEPTH_COMPONENT32 0x81A7
#define GL_ARRAY_BUFFER 0x8892
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_READ_FRAMEBUFFER 0x8CA8
#define GL_DRAW_FRAMEBUFFER 0x8CA9
#define GL_FRAMEBUFFER 0x8D40
#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6
#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA
#define GL_RENDERBUFFER 0x8D41
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_DEPTH_ATTACHMENT 0x8D00
#define GL_STENCIL_ATTACHMENT 0x8D20
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB
#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC
#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD
#define GL_COLOR_BUFFER_BIT 0x00004000
#define GL_DEPTH_BUFFER_BIT 0x00000100
#define GL_STENCIL_BUFFER_BIT 0x00000400
#define GL_PIXEL_PACK_BUFFER 0x88EB
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED
#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF
#define GL_UNPACK_ROW_LENGTH 0x0CF2
#define GL_UNPACK_ALIGNMENT 0x0CF5
#define GL_QUERY_RESULT 0x8866
#define GL_QUERY_RESULT_AVAILABLE 0x8867
#define GL_TIME_ELAPSED 0x88BF
#define GL_SAMPLES_PASSED 0x8914
#define GL_NEAREST 0x2600
#define GL_LINEAR 0x2601
#define GL_NEAREST_MIPMAP_NEAREST 0x2700
#define GL_NEAREST_MIPMAP_LINEAR 0x2702
#define GL_LINEAR_MIPMAP_NEAREST 0x2701
#define GL_LINEAR_MIPMAP_LINEAR 0x2703
#define GL_TEXTURE_WRAP_S 0x2802
#define GL_TEXTURE_WRAP_T 0x2803
#define GL_TEXTURE_MAG_FILTER 0x2800
#define GL_TEXTURE_MIN_FILTER 0x2801
#define GL_CLAMP_TO_EDGE 0x812F
#define GL_TEXTURE_2D 0x0DE1
#define GL_TEXTURE_3D 0x806F
#define GL_TEXTURE_2D_ARRAY 0x8C1A
#define GL_TEXTURE_RECTANGLE 0x84F5
#define GL_TEXTURE0 0x84C0
#define GL_TEXTURE1 0x84C1
#define GL_TEXTURE2 0x84C2
#define GL_TEXTURE3 0x84C3
#define GL_TEXTURE4 0x84C4
#define GL_TEXTURE5 0x84C5
#define GL_TEXTURE6 0x84C6
#define GL_TEXTURE7 0x84C7
#define GL_TEXTURE8 0x84C8
#define GL_TEXTURE9 0x84C9
#define GL_TEXTURE10 0x84CA
#define GL_TEXTURE11 0x84CB
#define GL_TEXTURE12 0x84CC
#define GL_TEXTURE13 0x84CD
#define GL_TEXTURE14 0x84CE
#define GL_TEXTURE15 0x84CF
#define GL_MAX_TEXTURE_UNITS 0x84E2
#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
#define GL_MAX_TEXTURE_SIZE 0x0D33
#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF
#define GL_VERTEX_SHADER 0x8B31
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_BLEND 0x0BE2
#define GL_ZERO 0
#define GL_ONE 1
#define GL_SRC_COLOR 0x0300
#define GL_ONE_MINUS_SRC_COLOR 0x0301
#define GL_SRC_ALPHA 0x0302
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
#define GL_DST_ALPHA 0x0304
#define GL_ONE_MINUS_DST_ALPHA 0x0305
#define GL_DST_COLOR 0x0306
#define GL_ONE_MINUS_DST_COLOR 0x0307
#define GL_CONSTANT_COLOR 0x8001
#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002
#define GL_CONSTANT_ALPHA 0x8003
#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
#define GL_SRC1_ALPHA 0x8589
#define GL_SRC1_COLOR 0x88F9
#define GL_ONE_MINUS_SRC1_COLOR 0x88FA
#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB
#define GL_FUNC_ADD 0x8006
#define GL_MIN 0x8007
#define GL_MAX 0x8008
#define GL_NEVER 0x0200
#define GL_LESS 0x0201
#define GL_EQUAL 0x0202
#define GL_LEQUAL 0x0203
#define GL_GREATER 0x0204
#define GL_NOTEQUAL 0x0205
#define GL_GEQUAL 0x0206
#define GL_ALWAYS 0x0207
#define GL_DEPTH_TEST 0x0B71
#define GL_DEPTH_WRITEMASK 0x0B72
#define GL_SCISSOR_TEST 0x0C11
#define GL_VENDOR 0x1F00
#define GL_RENDERER 0x1F01
#define GL_VERSION 0x1F02
#define GL_EXTENSIONS 0x1F03
#define GL_NUM_EXTENSIONS 0x821D
#define GL_MINOR_VERSION 0x821C
#define GL_MAJOR_VERSION 0x821B
#define GL_SHADING_LANGUAGE_VERSION 0x8B8C
#define GL_POINTS 0x0000
#define GL_LINES 0x0001
#define GL_LINE_LOOP 0x0002
#define GL_LINE_STRIP 0x0003
#define GL_TRIANGLES 0x0004
#define GL_TRIANGLE_STRIP 0x0005
#define GL_TRIANGLE_FAN 0x0006
#define GL_QUADS 0x0007
#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
#define GL_RGB_422_APPLE 0x8A1F
#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA
#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB
#define GL_RGB_RAW_422_APPLE 0x8A51
#define GL_MULTIPLY_KHR 0x9294
#define GL_SCREEN_KHR 0x9295
#define GL_OVERLAY_KHR 0x9296
#define GL_DARKEN_KHR 0x9297
#define GL_LIGHTEN_KHR 0x9298
#define GL_COLORDODGE_KHR 0x9299
#define GL_COLORBURN_KHR 0x929A
#define GL_HARDLIGHT_KHR 0x929B
#define GL_SOFTLIGHT_KHR 0x929C
#define GL_DIFFERENCE_KHR 0x929E
#define GL_EXCLUSION_KHR 0x92A0
#define GL_HSL_HUE_KHR 0x92AD
#define GL_HSL_SATURATION_KHR 0x92AE
#define GL_HSL_COLOR_KHR 0x92AF
#define GL_HSL_LUMINOSITY_KHR 0x92B0
#define SWGL_BLEND_DROP_SHADOW 0xB001
#define SWGL_BLEND_SUBPIXEL_TEXT 0xB002

File diff suppressed because it is too large Load diff

View file

@ -1,12 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![crate_name = "swgl"]
#![crate_type = "lib"]
extern crate gleam;
mod swgl_fns;
pub use crate::swgl_fns::*;

View file

@ -1,186 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
struct VertexAttrib;
namespace glsl {
// Type holding group of scalars interpolated across rasterized rows and spans,
// shuttling values between vertex shaders and fragment shaders.
// GCC requires power-of-two vector sizes, so must use glsl type as workaround
// to operate in Float-sized chunks.
typedef vec3 Interpolants;
// Clip distances, if enabled, are always stored in the first SIMD chunk of the
// interpolants.
static ALWAYS_INLINE Float get_clip_distances(const Interpolants& interp) {
return interp.x;
}
struct VertexShaderImpl;
struct FragmentShaderImpl;
struct ProgramImpl {
virtual ~ProgramImpl() {}
virtual int get_uniform(const char* name) const = 0;
virtual void bind_attrib(const char* name, int index) = 0;
virtual int get_attrib(const char* name) const = 0;
virtual size_t interpolants_size() const = 0;
virtual VertexShaderImpl* get_vertex_shader() = 0;
virtual FragmentShaderImpl* get_fragment_shader() = 0;
virtual const char* get_name() const = 0;
};
typedef ProgramImpl* (*ProgramLoader)();
// The maximum size of the gl_ClipDistance array.
constexpr int32_t gl_MaxClipDistances = 4;
struct VertexShaderImpl {
typedef void (*SetUniform1iFunc)(VertexShaderImpl*, int index, int value);
typedef void (*SetUniform4fvFunc)(VertexShaderImpl*, int index,
const float* value);
typedef void (*SetUniformMatrix4fvFunc)(VertexShaderImpl*, int index,
const float* value);
typedef void (*InitBatchFunc)(VertexShaderImpl*);
typedef void (*LoadAttribsFunc)(VertexShaderImpl*, VertexAttrib* attribs,
uint32_t start, int instance, int count);
typedef void (*RunPrimitiveFunc)(VertexShaderImpl*, char* interps,
size_t interp_stride);
SetUniform1iFunc set_uniform_1i_func = nullptr;
SetUniform4fvFunc set_uniform_4fv_func = nullptr;
SetUniformMatrix4fvFunc set_uniform_matrix4fv_func = nullptr;
InitBatchFunc init_batch_func = nullptr;
LoadAttribsFunc load_attribs_func = nullptr;
RunPrimitiveFunc run_primitive_func = nullptr;
enum FLAGS {
CLIP_DISTANCE = 1 << 0,
};
int flags = 0;
void enable_clip_distance() { flags |= CLIP_DISTANCE; }
ALWAYS_INLINE bool use_clip_distance() const {
return (flags & CLIP_DISTANCE) != 0;
}
vec4 gl_Position;
Float gl_ClipDistance[gl_MaxClipDistances];
void set_uniform_1i(int index, int value) {
(*set_uniform_1i_func)(this, index, value);
}
void set_uniform_4fv(int index, const float* value) {
(*set_uniform_4fv_func)(this, index, value);
}
void set_uniform_matrix4fv(int index, const float* value) {
(*set_uniform_matrix4fv_func)(this, index, value);
}
void init_batch() { (*init_batch_func)(this); }
ALWAYS_INLINE void load_attribs(VertexAttrib* attribs, uint32_t start,
int instance, int count) {
(*load_attribs_func)(this, attribs, start, instance, count);
}
ALWAYS_INLINE void run_primitive(char* interps, size_t interp_stride) {
(*run_primitive_func)(this, interps, interp_stride);
}
};
// The number of pixels in a step.
constexpr int32_t swgl_StepSize = 4;
struct FragmentShaderImpl {
typedef void (*InitSpanFunc)(FragmentShaderImpl*, const void* interps,
const void* step);
typedef void (*RunFunc)(FragmentShaderImpl*);
typedef void (*SkipFunc)(FragmentShaderImpl*, int steps);
typedef void (*InitSpanWFunc)(FragmentShaderImpl*, const void* interps,
const void* step);
typedef void (*RunWFunc)(FragmentShaderImpl*);
typedef void (*SkipWFunc)(FragmentShaderImpl*, int steps);
typedef int (*DrawSpanRGBA8Func)(FragmentShaderImpl*);
typedef int (*DrawSpanR8Func)(FragmentShaderImpl*);
InitSpanFunc init_span_func = nullptr;
RunFunc run_func = nullptr;
SkipFunc skip_func = nullptr;
InitSpanWFunc init_span_w_func = nullptr;
RunWFunc run_w_func = nullptr;
SkipWFunc skip_w_func = nullptr;
DrawSpanRGBA8Func draw_span_RGBA8_func = nullptr;
DrawSpanR8Func draw_span_R8_func = nullptr;
enum FLAGS {
DISCARD = 1 << 0,
PERSPECTIVE = 1 << 1,
};
int flags = 0;
void enable_discard() { flags |= DISCARD; }
void enable_perspective() { flags |= PERSPECTIVE; }
ALWAYS_INLINE bool use_discard() const { return (flags & DISCARD) != 0; }
ALWAYS_INLINE bool use_perspective() const {
return (flags & PERSPECTIVE) != 0;
}
vec4 gl_FragCoord;
vec4 gl_FragColor;
vec4 gl_SecondaryFragColor;
vec2_scalar swgl_StepZW;
Bool swgl_IsPixelDiscarded = false;
// The current buffer position for committing span output.
uint32_t* swgl_OutRGBA8 = nullptr;
uint8_t* swgl_OutR8 = nullptr;
// The remaining number of pixels in the span.
int32_t swgl_SpanLength = 0;
ALWAYS_INLINE void step_fragcoord(int steps = 4) { gl_FragCoord.x += steps; }
ALWAYS_INLINE void step_perspective(int steps = 4) {
gl_FragCoord.z += swgl_StepZW.x * steps;
gl_FragCoord.w += swgl_StepZW.y * steps;
}
template <bool W = false>
ALWAYS_INLINE void init_span(const void* interps, const void* step) {
(*(W ? init_span_w_func : init_span_func))(this, interps, step);
}
template <bool W = false>
ALWAYS_INLINE void run() {
(*(W ? run_w_func : run_func))(this);
}
template <bool W = false>
ALWAYS_INLINE void skip(int steps = 4) {
(*(W ? skip_w_func : skip_func))(this, steps);
}
ALWAYS_INLINE int draw_span(uint32_t* buf, int len) {
swgl_OutRGBA8 = buf;
swgl_SpanLength = len;
return (*draw_span_RGBA8_func)(this);
}
ALWAYS_INLINE bool has_draw_span(uint32_t*) {
return draw_span_RGBA8_func != nullptr;
}
ALWAYS_INLINE int draw_span(uint8_t* buf, int len) {
swgl_OutR8 = buf;
swgl_SpanLength = len;
return (*draw_span_R8_func)(this);
}
ALWAYS_INLINE bool has_draw_span(uint8_t*) {
return draw_span_R8_func != nullptr;
}
};
} // namespace glsl

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,563 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef __clang__
# ifdef __SSE2__
# include <xmmintrin.h>
# define USE_SSE2 1
# endif
# ifdef __ARM_NEON
# include <arm_neon.h>
# define USE_NEON 1
# endif
#endif
namespace glsl {
#ifdef __clang__
template <typename T, int N>
using VectorType = T __attribute__((ext_vector_type(N)));
# define CONVERT(vector, type) __builtin_convertvector(vector, type)
# define SHUFFLE(a, b, ...) __builtin_shufflevector(a, b, __VA_ARGS__)
template <typename T>
SI VectorType<T, 4> combine(VectorType<T, 2> a, VectorType<T, 2> b) {
return __builtin_shufflevector(a, b, 0, 1, 2, 3);
}
template <typename T>
SI VectorType<T, 8> combine(VectorType<T, 4> a, VectorType<T, 4> b) {
return __builtin_shufflevector(a, b, 0, 1, 2, 3, 4, 5, 6, 7);
}
template <typename T>
SI VectorType<T, 16> combine(VectorType<T, 8> a, VectorType<T, 8> b) {
return __builtin_shufflevector(a, b, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15);
}
template <typename T>
SI VectorType<T, 2> lowHalf(VectorType<T, 4> a) {
return __builtin_shufflevector(a, a, 0, 1);
}
template <typename T>
SI VectorType<T, 2> highHalf(VectorType<T, 4> a) {
return __builtin_shufflevector(a, a, 2, 3);
}
template <typename T>
SI VectorType<T, 4> lowHalf(VectorType<T, 8> a) {
return __builtin_shufflevector(a, a, 0, 1, 2, 3);
}
template <typename T>
SI VectorType<T, 4> highHalf(VectorType<T, 8> a) {
return __builtin_shufflevector(a, a, 4, 5, 6, 7);
}
template <typename T>
SI VectorType<T, 8> lowHalf(VectorType<T, 16> a) {
return __builtin_shufflevector(a, a, 0, 1, 2, 3, 4, 5, 6, 7);
}
template <typename T>
SI VectorType<T, 8> highHalf(VectorType<T, 16> a) {
return __builtin_shufflevector(a, a, 8, 9, 10, 11, 12, 13, 14, 15);
}
template <typename T>
SI VectorType<T, 8> expand(VectorType<T, 4> a) {
return __builtin_shufflevector(a, a, 0, 1, 2, 3, -1, -1, -1, -1);
}
#else
template <typename T>
struct VectorMask {
typedef T type;
};
template <>
struct VectorMask<uint32_t> {
typedef int32_t type;
};
template <>
struct VectorMask<uint16_t> {
typedef int16_t type;
};
template <>
struct VectorMask<uint8_t> {
typedef int8_t type;
};
template <>
struct VectorMask<float> {
typedef int type;
};
template <typename T, int N>
struct VectorType {
enum { SIZE = N };
typedef T data_type __attribute__((vector_size(sizeof(T) * N)));
typedef typename VectorMask<T>::type mask_index;
typedef mask_index mask_type
__attribute__((vector_size(sizeof(mask_index) * N)));
typedef T half_type __attribute__((vector_size(sizeof(T) * (N / 2))));
union {
data_type data;
struct {
T x, y, z, w;
};
T elements[N];
struct {
half_type low_half, high_half;
};
};
VectorType() : data{0} {}
constexpr VectorType(const VectorType& rhs) : data(rhs.data) {}
// GCC vector extensions only support broadcasting scalars on arithmetic ops,
// but not on initializers, hence the following...
constexpr VectorType(T n) : data((data_type){0} + n) {}
constexpr VectorType(T a, T b, T c, T d) : data{a, b, c, d} {}
constexpr VectorType(T a, T b, T c, T d, T e, T f, T g, T h)
: data{a, b, c, d, e, f, g, h} {}
constexpr VectorType(T a, T b, T c, T d, T e, T f, T g, T h, T i, T j, T k,
T l, T m, T n, T o, T p)
: data{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p} {}
SI VectorType wrap(const data_type& data) {
VectorType v;
v.data = data;
return v;
}
T& operator[](size_t i) { return elements[i]; }
T operator[](size_t i) const { return elements[i]; }
template <typename U>
operator VectorType<U, 2>() const {
return VectorType<U, 2>::wrap(
(typename VectorType<U, N>::data_type){U(x), U(y)});
}
template <typename U>
operator VectorType<U, 4>() const {
return VectorType<U, 4>::wrap(
(typename VectorType<U, N>::data_type){U(x), U(y), U(z), U(w)});
}
template <typename U>
operator VectorType<U, 8>() const {
return VectorType<U, 8>::wrap((typename VectorType<U, N>::data_type){
U(elements[0]), U(elements[1]), U(elements[2]), U(elements[3]),
U(elements[4]), U(elements[5]), U(elements[6]), U(elements[7])});
}
template <typename U>
operator VectorType<U, 16>() const {
return VectorType<U, 16>::wrap((typename VectorType<U, N>::data_type){
U(elements[0]),
U(elements[1]),
U(elements[2]),
U(elements[3]),
U(elements[4]),
U(elements[5]),
U(elements[6]),
U(elements[7]),
U(elements[8]),
U(elements[9]),
U(elements[10]),
U(elements[11]),
U(elements[12]),
U(elements[13]),
U(elements[14]),
U(elements[15]),
});
}
VectorType operator-() const { return wrap(-data); }
VectorType operator~() const { return wrap(~data); }
VectorType operator&(VectorType x) const { return wrap(data & x.data); }
VectorType operator&(T x) const { return wrap(data & x); }
VectorType operator|(VectorType x) const { return wrap(data | x.data); }
VectorType operator|(T x) const { return wrap(data | x); }
VectorType operator^(VectorType x) const { return wrap(data ^ x.data); }
VectorType operator^(T x) const { return wrap(data ^ x); }
VectorType operator<<(int x) const { return wrap(data << x); }
VectorType operator>>(int x) const { return wrap(data >> x); }
VectorType operator+(VectorType x) const { return wrap(data + x.data); }
VectorType operator+(T x) const { return wrap(data + x); }
friend VectorType operator+(T x, VectorType y) { return wrap(x + y.data); }
VectorType operator-(VectorType x) const { return wrap(data - x.data); }
VectorType operator-(T x) const { return wrap(data - x); }
friend VectorType operator-(T x, VectorType y) { return wrap(x - y.data); }
VectorType operator*(VectorType x) const { return wrap(data * x.data); }
VectorType operator*(T x) const { return wrap(data * x); }
friend VectorType operator*(T x, VectorType y) { return wrap(x * y.data); }
VectorType operator/(VectorType x) const { return wrap(data / x.data); }
VectorType operator/(T x) const { return wrap(data / x); }
friend VectorType operator/(T x, VectorType y) { return wrap(x / y.data); }
VectorType operator%(int x) const { return wrap(data % x); }
VectorType& operator&=(VectorType x) {
data &= x.data;
return *this;
}
VectorType& operator|=(VectorType x) {
data |= x.data;
return *this;
}
VectorType& operator^=(VectorType x) {
data ^= x.data;
return *this;
}
VectorType& operator<<=(int x) {
data <<= x;
return *this;
}
VectorType& operator>>=(int x) {
data >>= x;
return *this;
}
VectorType& operator+=(VectorType x) {
data += x.data;
return *this;
}
VectorType& operator-=(VectorType x) {
data -= x.data;
return *this;
}
VectorType& operator*=(VectorType x) {
data *= x.data;
return *this;
}
VectorType& operator/=(VectorType x) {
data /= x.data;
return *this;
}
VectorType& operator%=(int x) {
data %= x;
return *this;
}
VectorType<mask_type, N> operator==(VectorType x) const {
return VectorType<mask_type, N>::wrap(data == x.data);
}
VectorType<mask_type, N> operator!=(VectorType x) const {
return VectorType<mask_type, N>::wrap(data != x.data);
}
VectorType<mask_type, N> operator<(VectorType x) const {
return VectorType<mask_type, N>::wrap(data < x.data);
}
VectorType<mask_type, N> operator>(VectorType x) const {
return VectorType<mask_type, N>::wrap(data > x.data);
}
VectorType<mask_type, N> operator<=(VectorType x) const {
return VectorType<mask_type, N>::wrap(data <= x.data);
}
VectorType<mask_type, N> operator>=(VectorType x) const {
return VectorType<mask_type, N>::wrap(data >= x.data);
}
VectorType operator!() const { return wrap(!data); }
VectorType operator&&(VectorType x) const { return wrap(data & x.data); }
VectorType operator||(VectorType x) const { return wrap(data | x.data); }
VectorType& operator=(VectorType x) {
data = x.data;
return *this;
}
VectorType<T, 4> shuffle(VectorType b, mask_index x, mask_index y,
mask_index z, mask_index w) const {
return VectorType<T, 4>::wrap(__builtin_shuffle(
data, b.data, (typename VectorType<T, 4>::mask_type){x, y, z, w}));
}
VectorType<T, 8> shuffle(VectorType b, mask_index x, mask_index y,
mask_index z, mask_index w, mask_index s,
mask_index t, mask_index u, mask_index v) const {
return VectorType<T, 8>::wrap(__builtin_shuffle(
data, b.data,
(typename VectorType<T, 8>::mask_type){x, y, z, w, s, t, u, v}));
}
VectorType<T, 16> shuffle(VectorType b, mask_index x, mask_index y,
mask_index z, mask_index w, mask_index s,
mask_index t, mask_index u, mask_index v,
mask_index i, mask_index j, mask_index k,
mask_index l, mask_index m, mask_index n,
mask_index o, mask_index p) const {
return VectorType<T, 16>::wrap(
__builtin_shuffle(data, b.data,
(typename VectorType<T, 16>::mask_type){
x, y, z, w, s, t, u, v, i, j, k, l, m, n, o, p}));
}
VectorType<T, 4> swizzle(mask_index x, mask_index y, mask_index z,
mask_index w) const {
return VectorType<T, 4>::wrap(__builtin_shuffle(
data, (typename VectorType<T, 4>::mask_type){x, y, z, w}));
}
VectorType<T, 8> swizzle(mask_index x, mask_index y, mask_index z,
mask_index w, mask_index s, mask_index t,
mask_index u, mask_index v) const {
return VectorType<T, 8>::wrap(__builtin_shuffle(
data, (typename VectorType<T, 8>::mask_type){x, y, z, w, s, t, u, v}));
}
SI VectorType wrap(half_type low, half_type high) {
VectorType v;
v.low_half = low;
v.high_half = high;
return v;
}
VectorType<T, N * 2> combine(VectorType high) const {
return VectorType<T, N * 2>::wrap(data, high.data);
}
# define xxxx swizzle(0, 0, 0, 0)
# define yyyy swizzle(1, 1, 1, 1)
# define zzzz swizzle(2, 2, 2, 2)
# define wwww swizzle(3, 3, 3, 3)
# define xxyy swizzle(0, 0, 1, 1)
# define xxzz swizzle(0, 0, 2, 2)
# define yyww swizzle(1, 1, 3, 3)
# define zzww swizzle(2, 2, 3, 3)
# define xyxy swizzle(0, 1, 0, 1)
# define xzxz swizzle(0, 2, 0, 2)
# define ywyw swizzle(1, 3, 1, 3)
# define zwzw swizzle(2, 3, 2, 3)
# define zwxy swizzle(2, 3, 0, 1)
# define zyxw swizzle(2, 1, 0, 3)
# define xxyz swizzle(0, 0, 1, 2)
# define xyyz swizzle(0, 1, 1, 2)
# define xyzz swizzle(0, 1, 2, 2)
# define xzyw swizzle(0, 2, 1, 3)
# define yzwx swizzle(1, 2, 3, 0)
# define wxyz swizzle(3, 0, 1, 2)
# define wzyx swizzle(3, 2, 1, 0)
# define xxxxyyyy XXXXYYYY()
VectorType<T, 8> XXXXYYYY() const {
return swizzle(0, 0, 0, 0).combine(swizzle(1, 1, 1, 1));
}
# define zzzzwwww ZZZZWWWW()
VectorType<T, 8> ZZZZWWWW() const {
return swizzle(2, 2, 2, 2).combine(swizzle(3, 3, 3, 3));
}
# define xyzwxyzw XYZWXYZW()
VectorType<T, 8> XYZWXYZW() const { return combine(*this); }
# define xyxyxyxy XYXYXYXY()
VectorType<T, 8> XYXYXYXY() const {
return swizzle(0, 1, 0, 1).combine(swizzle(0, 1, 0, 1));
}
# define zwzwzwzw ZWZWZWZW()
VectorType<T, 8> ZWZWZWZW() const {
return swizzle(2, 3, 2, 3).combine(swizzle(2, 3, 2, 3));
}
# define xxyyzzww XXYYZZWW()
VectorType<T, 8> XXYYZZWW() const {
return swizzle(0, 0, 1, 1).combine(swizzle(2, 2, 3, 3));
}
# define xxxxyyyyzzzzwwww XXXXYYYYZZZZWWWW()
VectorType<T, 16> XXXXYYYYZZZZWWWW() {
return XXXXYYYY().combine(ZZZZWWWW());
}
};
template <typename T>
struct VectorType<T, 2> {
typedef T data_type __attribute__((vector_size(sizeof(T) * 2)));
union {
data_type data;
struct {
T x, y;
};
T elements[2];
};
SI VectorType wrap(const data_type& data) {
VectorType v;
v.data = data;
return v;
}
VectorType operator&(VectorType x) const { return wrap(data & x.data); }
VectorType operator&(T x) const { return wrap(data & x); }
VectorType operator|(VectorType x) const { return wrap(data | x.data); }
VectorType operator|(T x) const { return wrap(data | x); }
};
# define CONVERT(vector, type) ((type)(vector))
# define SHUFFLE(a, b, ...) a.shuffle(b, __VA_ARGS__)
template <typename T, int N>
SI VectorType<T, N * 2> combine(VectorType<T, N> a, VectorType<T, N> b) {
return VectorType<T, N * 2>::wrap(a.data, b.data);
}
template <typename T, int N>
SI VectorType<T, N / 2> lowHalf(VectorType<T, N> a) {
return VectorType<T, N / 2>::wrap(a.low_half);
}
template <typename T, int N>
SI VectorType<T, N / 2> highHalf(VectorType<T, N> a) {
return VectorType<T, N / 2>::wrap(a.high_half);
}
template <typename T, int N>
SI VectorType<T, N * 2> expand(VectorType<T, N> a) {
return combine(a, a);
}
#endif
template <typename T, int N>
SI VectorType<T, N * 4> combine(VectorType<T, N> a, VectorType<T, N> b,
VectorType<T, N> c, VectorType<T, N> d) {
return combine(combine(a, b), combine(c, d));
}
template <typename T, int N>
SI VectorType<T, N> combineLow(VectorType<T, N> a, VectorType<T, N> b) {
return combine(lowHalf(a), lowHalf(b));
}
template <typename T, int N>
SI VectorType<T, N> combineHigh(VectorType<T, N> a, VectorType<T, N> b) {
return combine(highHalf(a), highHalf(b));
}
template <typename T, int N>
SI VectorType<T, N * 2> repeat2(VectorType<T, N> a) {
return combine(a, a);
}
template <typename T, int N>
SI VectorType<T, N * 4> repeat4(VectorType<T, N> a) {
return combine(a, a, a, a);
}
template <typename T>
SI VectorType<T, 4> zipLow(VectorType<T, 4> a, VectorType<T, 4> b) {
return SHUFFLE(a, b, 0, 4, 1, 5);
}
template <typename T>
SI VectorType<T, 4> zipHigh(VectorType<T, 4> a, VectorType<T, 4> b) {
return SHUFFLE(a, b, 2, 6, 3, 7);
}
template <typename T>
SI VectorType<T, 8> zipLow(VectorType<T, 8> a, VectorType<T, 8> b) {
return SHUFFLE(a, b, 0, 8, 1, 9, 2, 10, 3, 11);
}
template <typename T>
SI VectorType<T, 8> zipHigh(VectorType<T, 8> a, VectorType<T, 8> b) {
return SHUFFLE(a, b, 4, 12, 5, 13, 6, 14, 7, 15);
}
template <typename T>
SI VectorType<T, 16> zipLow(VectorType<T, 16> a, VectorType<T, 16> b) {
return SHUFFLE(a, b, 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23);
}
template <typename T>
SI VectorType<T, 16> zipHigh(VectorType<T, 16> a, VectorType<T, 16> b) {
return SHUFFLE(a, b, 8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30,
31);
}
template <typename T>
SI VectorType<T, 8> zip2Low(VectorType<T, 8> a, VectorType<T, 8> b) {
return SHUFFLE(a, b, 0, 1, 8, 9, 2, 3, 10, 11);
}
template <typename T>
SI VectorType<T, 8> zip2High(VectorType<T, 8> a, VectorType<T, 8> b) {
return SHUFFLE(a, b, 4, 5, 12, 13, 6, 7, 14, 15);
}
#ifdef __clang__
template <typename T>
SI VectorType<T, 8> zip(VectorType<T, 4> a, VectorType<T, 4> b) {
return SHUFFLE(a, b, 0, 4, 1, 5, 2, 6, 3, 7);
}
template <typename T>
SI VectorType<T, 16> zip(VectorType<T, 8> a, VectorType<T, 8> b) {
return SHUFFLE(a, b, 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15);
}
#else
template <typename T, int N>
SI VectorType<T, N * 2> zip(VectorType<T, N> a, VectorType<T, N> b) {
return combine(zipLow(a, b), zipHigh(a, b));
}
#endif
template <typename T>
struct Unaligned {
template <typename P>
SI T load(const P* p) {
T v;
memcpy(&v, p, sizeof(v));
return v;
}
template <typename P>
SI void store(P* p, T v) {
memcpy(p, &v, sizeof(v));
}
};
#ifndef __clang__
template <typename T, int N>
struct Unaligned<VectorType<T, N>> {
template <typename P>
SI VectorType<T, N> load(const P* p) {
VectorType<T, N> v;
memcpy(v.elements, p, sizeof(v));
return v;
}
template <typename P>
SI void store(P* p, VectorType<T, N> v) {
memcpy(p, v.elements, sizeof(v));
}
};
#endif
template <typename T, typename P>
SI T unaligned_load(const P* p) {
return Unaligned<T>::load(p);
}
template <typename T, typename P>
SI void unaligned_store(P* p, T v) {
Unaligned<T>::store(p, v);
}
template <typename D, typename S>
SI D bit_cast(const S& src) {
static_assert(sizeof(D) == sizeof(S), "");
return unaligned_load<D>(&src);
}
template <typename T>
using V2 = VectorType<T, 2>;
template <typename T>
using V4 = VectorType<T, 4>;
using Float = V4<float>;
using I32 = V4<int32_t>;
using I16 = V4<int16_t>;
using U64 = V4<uint64_t>;
using U32 = V4<uint32_t>;
using U16 = V4<uint16_t>;
using U8 = V4<uint8_t>;
using Bool = V4<int>;
template <typename T>
using V8 = VectorType<T, 8>;
template <typename T>
using V16 = VectorType<T, 16>;
} // namespace glsl

View file

@ -1,15 +0,0 @@
[package]
name = "tileview"
version = "0.1.0"
authors = ["Bert Peers <bpeers@mozilla.com>"]
license = "MPL-2.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ron = "0.6.2"
serde = {version = "1.0.88", features = ["derive"] }
webrender = {path = "../webrender", features=["capture","replay","png","profiler","no_static_freetype", "leak_checks"]}
webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]}
euclid = { version = "0.22.0", features = ["serde"] }

View file

@ -1,724 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/// Command line tool to convert logged tile cache files into a visualization.
///
/// Steps to use this:
/// 1. enable webrender; enable gfx.webrender.debug.tile-cache-logging
/// 2. take a capture using ctrl-shift-3
/// if all is well, there will be a .../wr-capture/tilecache folder with *.ron files
/// 3. run tileview with that folder as the first parameter and some empty output folder as the
/// 2nd:
/// cargo run --release -- /foo/bar/wr-capture/tilecache /tmp/tilecache
/// 4. open /tmp/tilecache/index.html
///
/// Note: accurate interning info requires that the circular buffer doesn't wrap around.
/// So for best results, use this workflow:
/// a. start up blank browser; in about:config enable logging; close browser
/// b. start new browser, quickly load the repro
/// c. capture.
///
/// If that's tricky, you can also just throw more memory at it: in render_backend.rs,
/// increase the buffer size here: 'TileCacheLogger::new(500usize)'
///
/// Note: some features don't work when opening index.html directly due to cross-scripting
/// protections. Instead use a HTTP server:
/// python -m SimpleHTTPServer 8000
use webrender::{TileNode, TileNodeKind, InvalidationReason, TileOffset};
use webrender::{TileSerializer, TileCacheInstanceSerializer, TileCacheLoggerUpdateLists};
use webrender::{PrimitiveCompareResultDetail, CompareHelperResult, ItemUid};
use serde::Deserialize;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::ffi::OsString;
use std::collections::HashMap;
use webrender::enumerate_interners;
use webrender::api::ColorF;
use euclid::{Rect, Transform3D};
use webrender_api::units::{PicturePoint, PictureSize, PicturePixel, WorldPixel};
static RES_JAVASCRIPT: &'static str = include_str!("tilecache.js");
static RES_BASE_CSS: &'static str = include_str!("tilecache_base.css");
#[derive(Deserialize)]
pub struct Slice {
pub transform: Transform3D<f32, PicturePixel, WorldPixel>,
pub tile_cache: TileCacheInstanceSerializer
}
// invalidation reason CSS colors
static CSS_FRACTIONAL_OFFSET: &str = "fill:#4040c0;fill-opacity:0.1;";
static CSS_BACKGROUND_COLOR: &str = "fill:#10c070;fill-opacity:0.1;";
static CSS_SURFACE_OPACITY_CHANNEL: &str = "fill:#c040c0;fill-opacity:0.1;";
static CSS_NO_TEXTURE: &str = "fill:#c04040;fill-opacity:0.1;";
static CSS_NO_SURFACE: &str = "fill:#40c040;fill-opacity:0.1;";
static CSS_PRIM_COUNT: &str = "fill:#40f0f0;fill-opacity:0.1;";
static CSS_CONTENT: &str = "fill:#f04040;fill-opacity:0.1;";
static CSS_COMPOSITOR_KIND_CHANGED: &str = "fill:#f0c070;fill-opacity:0.1;";
static CSS_VALID_RECT_CHANGED: &str = "fill:#ff00ff;fill-opacity:0.1;";
static CSS_SCALE_CHANGED: &str = "fill:#ff80ff;fill-opacity:0.1;";
// parameters to tweak the SVG generation
struct SvgSettings {
pub scale: f32,
pub x: f32,
pub y: f32,
}
fn tile_node_to_svg(node: &TileNode,
transform: &Transform3D<f32, PicturePixel, WorldPixel>,
svg_settings: &SvgSettings) -> String
{
match &node.kind {
TileNodeKind::Leaf { .. } => {
let rect_world = transform.outer_transformed_rect(&node.rect.to_rect()).unwrap();
format!("<rect x=\"{:.2}\" y=\"{:.2}\" width=\"{:.2}\" height=\"{:.2}\" />\n",
rect_world.origin.x * svg_settings.scale + svg_settings.x,
rect_world.origin.y * svg_settings.scale + svg_settings.y,
rect_world.size.width * svg_settings.scale,
rect_world.size.height * svg_settings.scale)
},
TileNodeKind::Node { children } => {
children.iter().fold(String::new(), |acc, child| acc + &tile_node_to_svg(child, transform, svg_settings) )
}
}
}
fn tile_to_svg(key: TileOffset,
tile: &TileSerializer,
slice: &Slice,
prev_tile: Option<&TileSerializer>,
itemuid_to_string: &HashMap<ItemUid, String>,
tile_stroke: &str,
prim_class: &str,
invalidation_report: &mut String,
svg_width: &mut i32, svg_height: &mut i32,
svg_settings: &SvgSettings) -> String
{
let mut svg = format!("\n<!-- tile key {},{} ; -->\n", key.x, key.y);
let tile_fill =
match tile.invalidation_reason {
Some(InvalidationReason::FractionalOffset { .. }) => CSS_FRACTIONAL_OFFSET.to_string(),
Some(InvalidationReason::BackgroundColor { .. }) => CSS_BACKGROUND_COLOR.to_string(),
Some(InvalidationReason::SurfaceOpacityChanged { .. }) => CSS_SURFACE_OPACITY_CHANNEL.to_string(),
Some(InvalidationReason::NoTexture) => CSS_NO_TEXTURE.to_string(),
Some(InvalidationReason::NoSurface) => CSS_NO_SURFACE.to_string(),
Some(InvalidationReason::PrimCount { .. }) => CSS_PRIM_COUNT.to_string(),
Some(InvalidationReason::CompositorKindChanged) => CSS_COMPOSITOR_KIND_CHANGED.to_string(),
Some(InvalidationReason::Content { .. } ) => CSS_CONTENT.to_string(),
Some(InvalidationReason::ValidRectChanged) => CSS_VALID_RECT_CHANGED.to_string(),
Some(InvalidationReason::ScaleChanged) => CSS_SCALE_CHANGED.to_string(),
None => {
let mut background = tile.background_color;
if background.is_none() {
background = slice.tile_cache.background_color
}
match background {
Some(color) => {
let rgb = ( (color.r * 255.0) as u8,
(color.g * 255.0) as u8,
(color.b * 255.0) as u8 );
format!("fill:rgb({},{},{});fill-opacity:0.3;", rgb.0, rgb.1, rgb.2)
}
None => "fill:none;".to_string()
}
}
};
//let tile_style = format!("{}{}", tile_fill, tile_stroke);
let tile_style = format!("{}stroke:none;", tile_fill);
let title = match tile.invalidation_reason {
Some(_) => format!("<title>slice {} tile ({},{}) - {:?}</title>",
slice.tile_cache.slice, key.x, key.y,
tile.invalidation_reason),
None => String::new()
};
if let Some(reason) = &tile.invalidation_reason {
invalidation_report.push_str(
&format!("<div class=\"subheader\">slice {} key ({},{})</div><div class=\"data\">",
slice.tile_cache.slice,
key.x, key.y));
// go through most reasons individually so we can print something nicer than
// the default debug formatting of old and new:
match reason {
InvalidationReason::FractionalOffset { old, new } => {
invalidation_report.push_str(
&format!("<b>FractionalOffset</b> changed from ({},{}) to ({},{})",
old.x, old.y, new.x, new.y));
},
InvalidationReason::BackgroundColor { old, new } => {
fn to_str(c: &Option<ColorF>) -> String {
if let Some(c) = c {
format!("({},{},{},{})", c.r, c.g, c.b, c.a)
} else {
"none".to_string()
}
}
invalidation_report.push_str(
&format!("<b>BackGroundColor</b> changed from {} to {}",
to_str(old), to_str(new)));
},
InvalidationReason::SurfaceOpacityChanged { became_opaque } => {
invalidation_report.push_str(
&format!("<b>SurfaceOpacityChanged</b> changed from {} to {}",
!became_opaque, became_opaque));
},
InvalidationReason::PrimCount { old, new } => {
// diff the lists to find removed and added ItemUids,
// and convert them to strings to pretty-print what changed:
let old = old.as_ref().unwrap();
let new = new.as_ref().unwrap();
let removed = old.iter()
.filter(|i| !new.contains(i))
.fold(String::new(),
|acc, i| acc + "<li>" + &(i.get_uid()).to_string() + "..."
+ &itemuid_to_string.get(i).unwrap_or(&String::new())
+ "</li>\n");
let added = new.iter()
.filter(|i| !old.contains(i))
.fold(String::new(),
|acc, i| acc + "<li>" + &(i.get_uid()).to_string() + "..."
+ &itemuid_to_string.get(i).unwrap_or(&String::new())
+ "</li>\n");
invalidation_report.push_str(
&format!("<b>PrimCount</b> changed from {} to {}:<br/>\
removed:<ul>{}</ul>
added:<ul>{}</ul>",
old.len(), new.len(),
removed, added));
},
InvalidationReason::Content { prim_compare_result, prim_compare_result_detail } => {
let _ = prim_compare_result;
match prim_compare_result_detail {
Some(PrimitiveCompareResultDetail::Descriptor { old, new }) => {
if old.prim_uid == new.prim_uid {
// if the prim uid hasn't changed then try to print something useful
invalidation_report.push_str(
&format!("<b>Content: Descriptor</b> changed for uid {}<br/>",
old.prim_uid.get_uid()));
let mut changes = String::new();
if old.prim_clip_box != new.prim_clip_box {
changes += &format!("<li><b>prim_clip_rect</b> changed from {},{} -> {},{}",
old.prim_clip_box.min.x,
old.prim_clip_box.min.y,
old.prim_clip_box.max.x,
old.prim_clip_box.max.y);
changes += &format!(" to {},{} -> {},{}</li>",
new.prim_clip_box.min.x,
new.prim_clip_box.min.y,
new.prim_clip_box.max.x,
new.prim_clip_box.max.y);
}
invalidation_report.push_str(
&format!("<ul>{}<li>Item: {}</li></ul>",
changes,
&itemuid_to_string.get(&old.prim_uid).unwrap_or(&String::new())));
} else {
// .. if prim UIDs have changed, just dump both items and descriptors.
invalidation_report.push_str(
&format!("<b>Content: Descriptor</b> changed; old uid {}, new uid {}:<br/>",
old.prim_uid.get_uid(),
new.prim_uid.get_uid()));
invalidation_report.push_str(
&format!("old:<ul><li>Desc: {:?}</li><li>Item: {}</li></ul>",
old,
&itemuid_to_string.get(&old.prim_uid).unwrap_or(&String::new())));
invalidation_report.push_str(
&format!("new:<ul><li>Desc: {:?}</li><li>Item: {}</li></ul>",
new,
&itemuid_to_string.get(&new.prim_uid).unwrap_or(&String::new())));
}
},
Some(PrimitiveCompareResultDetail::Clip { detail }) => {
match detail {
CompareHelperResult::Count { prev_count, curr_count } => {
invalidation_report.push_str(
&format!("<b>Content: Clip</b> count changed from {} to {}<br/>",
prev_count, curr_count ));
},
CompareHelperResult::NotEqual { prev, curr } => {
invalidation_report.push_str(
&format!("<b>Content: Clip</b> ItemUids changed from {} to {}:<br/>",
prev.get_uid(), curr.get_uid() ));
invalidation_report.push_str(
&format!("old:<ul><li>{}</li></ul>",
&itemuid_to_string.get(&prev).unwrap_or(&String::new())));
invalidation_report.push_str(
&format!("new:<ul><li>{}</li></ul>",
&itemuid_to_string.get(&curr).unwrap_or(&String::new())));
},
reason => {
invalidation_report.push_str(&format!("{:?}", reason));
},
}
},
reason => {
invalidation_report.push_str(&format!("{:?}", reason));
},
}
},
reason => {
invalidation_report.push_str(&format!("{:?}", reason));
},
}
invalidation_report.push_str("</div>\n");
}
svg += &format!(r#"<rect x="{}" y="{}" width="{}" height="{}" style="{}" ></rect>"#,
tile.rect.origin.x * svg_settings.scale + svg_settings.x,
tile.rect.origin.y * svg_settings.scale + svg_settings.y,
tile.rect.size.width * svg_settings.scale,
tile.rect.size.height * svg_settings.scale,
tile_style);
svg += &format!("\n\n<g class=\"svg_quadtree\">\n{}</g>\n",
tile_node_to_svg(&tile.root, &slice.transform, svg_settings));
let right = (tile.rect.origin.x + tile.rect.size.width) as i32;
let bottom = (tile.rect.origin.y + tile.rect.size.height) as i32;
*svg_width = if right > *svg_width { right } else { *svg_width };
*svg_height = if bottom > *svg_height { bottom } else { *svg_height };
svg += "\n<!-- primitives -->\n";
svg += &format!("<g id=\"{}\">\n\t", prim_class);
let rect_visual_id = Rect {
origin: tile.rect.origin,
size: PictureSize::new(1.0, 1.0)
};
let rect_visual_id_world = slice.transform.outer_transformed_rect(&rect_visual_id).unwrap();
svg += &format!("\n<text class=\"svg_tile_visual_id\" x=\"{}\" y=\"{}\">{},{} ({})</text>",
rect_visual_id_world.origin.x * svg_settings.scale + svg_settings.x,
(rect_visual_id_world.origin.y + 110.0) * svg_settings.scale + svg_settings.y,
key.x, key.y, slice.tile_cache.slice);
for prim in &tile.current_descriptor.prims {
let rect = prim.prim_clip_box;
// the transform could also be part of the CSS, let the browser do it;
// might be a bit faster and also enable actual 3D transforms.
let rect_pixel = Rect {
origin: PicturePoint::new(rect.min.x, rect.min.y),
size: PictureSize::new(rect.max.x - rect.min.x, rect.max.y - rect.min.y),
};
let rect_world = slice.transform.outer_transformed_rect(&rect_pixel).unwrap();
let style =
if let Some(prev_tile) = prev_tile {
// when this O(n^2) gets too slow, stop brute-forcing and use a set or something
if prev_tile.current_descriptor.prims.iter().find(|&prim| prim.prim_clip_box == rect).is_some() {
""
} else {
"class=\"svg_changed_prim\" "
}
} else {
"class=\"svg_changed_prim\" "
};
svg += &format!("<rect x=\"{:.2}\" y=\"{:.2}\" width=\"{:.2}\" height=\"{:.2}\" {}/>",
rect_world.origin.x * svg_settings.scale + svg_settings.x,
rect_world.origin.y * svg_settings.scale + svg_settings.y,
rect_world.size.width * svg_settings.scale,
rect_world.size.height * svg_settings.scale,
style);
svg += "\n\t";
}
svg += "\n</g>\n";
// nearly invisible, all we want is the toolip really
let style = "style=\"fill-opacity:0.001;";
svg += &format!("<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" {}{}\" >{}<\u{2f}rect>",
tile.rect.origin.x * svg_settings.scale + svg_settings.x,
tile.rect.origin.y * svg_settings.scale + svg_settings.y,
tile.rect.size.width * svg_settings.scale,
tile.rect.size.height * svg_settings.scale,
style,
tile_stroke,
title);
svg
}
fn slices_to_svg(slices: &[Slice], prev_slices: Option<Vec<Slice>>,
itemuid_to_string: &HashMap<ItemUid, String>,
svg_width: &mut i32, svg_height: &mut i32,
max_slice_index: &mut usize,
svg_settings: &SvgSettings) -> (String, String)
{
let svg_begin = "<?xml\u{2d}stylesheet type\u{3d}\"text/css\" href\u{3d}\"tilecache_base.css\" ?>\n\
<?xml\u{2d}stylesheet type\u{3d}\"text/css\" href\u{3d}\"tilecache.css\" ?>\n";
let mut svg = String::new();
let mut invalidation_report = "<div class=\"header\">Invalidation</div>\n".to_string();
for slice in slices {
let tile_cache = &slice.tile_cache;
*max_slice_index = if tile_cache.slice > *max_slice_index { tile_cache.slice } else { *max_slice_index };
invalidation_report.push_str(&format!("<div id=\"invalidation_slice{}\">\n", tile_cache.slice));
let prim_class = format!("tile_slice{}", tile_cache.slice);
svg += &format!("\n<g id=\"tile_slice{}_everything\">", tile_cache.slice);
//println!("slice {}", tile_cache.slice);
svg += &format!("\n<!-- tile_cache slice {} -->\n",
tile_cache.slice);
//let tile_stroke = "stroke:grey;stroke-width:1;".to_string();
let tile_stroke = "stroke:none;".to_string();
let mut prev_slice = None;
if let Some(prev) = &prev_slices {
for prev_search in prev {
if prev_search.tile_cache.slice == tile_cache.slice {
prev_slice = Some(prev_search);
break;
}
}
}
for (key, tile) in &tile_cache.tiles {
let mut prev_tile = None;
if let Some(prev) = prev_slice {
prev_tile = prev.tile_cache.tiles.get(key);
}
svg += &tile_to_svg(*key, &tile, &slice, prev_tile,
itemuid_to_string,
&tile_stroke, &prim_class,
&mut invalidation_report,
svg_width, svg_height, svg_settings);
}
svg += "\n</g>";
invalidation_report.push_str("</div>\n");
}
(
format!("{}<svg version=\"1.1\" baseProfile=\"full\" xmlns=\"http://www.w3.org/2000/svg\" \
width=\"{}\" height=\"{}\" >",
svg_begin,
svg_width,
svg_height)
+ "\n"
+ "<rect fill=\"black\" width=\"100%\" height=\"100%\"/>\n"
+ &svg
+ "\n</svg>\n",
invalidation_report
)
}
fn write_html(output_dir: &Path, max_slice_index: usize, svg_files: &[String], intern_files: &[String]) {
let html_head = "<!DOCTYPE html>\n\
<html>\n\
<head>\n\
<meta charset=\"UTF-8\">\n\
<link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache_base.css\"></link>\n\
<link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache.css\"></link>\n\
</head>\n"
.to_string();
let html_body = "<body bgcolor=\"#000000\" onload=\"load()\">\n"
.to_string();
let mut script = "\n<script>\n".to_string();
script = format!("{}var svg_files = [\n", script);
for svg_file in svg_files {
script = format!("{} \"{}\",\n", script, svg_file);
}
script = format!("{}];\n\n", script);
script = format!("{}var intern_files = [\n", script);
for intern_file in intern_files {
script = format!("{} \"{}\",\n", script, intern_file);
}
script = format!("{}];\n</script>\n\n", script);
script = format!("{}<script src=\"tilecache.js\" type=\"text/javascript\"></script>\n\n", script);
let html_end = "</body>\n\
</html>\n"
.to_string();
let mut html_slices_form =
"\n<form id=\"slicecontrols\">\n\
Slice\n".to_string();
for ix in 0..max_slice_index + 1 {
html_slices_form +=
&format!(
"<input id=\"slice_toggle{}\" \
type=\"checkbox\" \
onchange=\"update_slice_visibility({})\" \
checked=\"checked\" />\n\
<label for=\"slice_toggle{}\">{}</label>\n",
ix,
max_slice_index + 1,
ix,
ix );
}
html_slices_form += "<form>\n";
let html_body = format!(
"{}\n\
<div class=\"split left\">\n\
<div>\n\
<object id=\"svg_container0\" type=\"image/svg+xml\" data=\"{}\" class=\"tile_svg\" ></object>\n\
<object id=\"svg_container1\" type=\"image/svg+xml\" data=\"{}\" class=\"tile_svg\" ></object>\n\
</div>\n\
</div>\n\
\n\
<div class=\"split right\">\n\
<iframe width=\"100%\" id=\"intern\" src=\"{}\"></iframe>\n\
</div>\n\
\n\
<div id=\"svg_ui_overlay\">\n\
<div id=\"text_frame_counter\">{}</div>\n\
<div id=\"text_spacebar\">Spacebar to Play</div>\n\
<div>Use Left/Right to Step</div>\n\
<input id=\"frame_slider\" type=\"range\" min=\"0\" max=\"{}\" value=\"0\" class=\"svg_ui_slider\" />
{}
</div>",
html_body,
svg_files[0],
svg_files[0],
intern_files[0],
svg_files[0],
svg_files.len(),
html_slices_form );
let html = format!("{}{}{}{}", html_head, html_body, script, html_end);
let output_file = output_dir.join("index.html");
let mut html_output = File::create(output_file).unwrap();
html_output.write_all(html.as_bytes()).unwrap();
}
fn write_css(output_dir: &Path, max_slice_index: usize, svg_settings: &SvgSettings) {
let mut css = String::new();
for ix in 0..max_slice_index + 1 {
let color = ( ix % 7 ) + 1;
let rgb = format!("rgb({},{},{})",
if color & 2 != 0 { 205 } else { 90 },
if color & 4 != 0 { 205 } else { 90 },
if color & 1 != 0 { 225 } else { 90 });
let prim_class = format!("tile_slice{}", ix);
css += &format!("#{} {{\n\
fill: {};\n\
fill-opacity: 0.03;\n\
stroke-width: {};\n\
stroke: {};\n\
}}\n\n",
prim_class,
//rgb,
"none",
0.8 * svg_settings.scale,
rgb);
}
css += &format!(".svg_tile_visual_id {{\n\
font: {}px sans-serif;\n\
fill: rgb(50,50,50);\n\
}}\n\n",
150.0 * svg_settings.scale);
let output_file = output_dir.join("tilecache.css");
let mut css_output = File::create(output_file).unwrap();
css_output.write_all(css.as_bytes()).unwrap();
}
macro_rules! updatelist_to_html_macro {
( $( $name:ident: $ty:ty, )+ ) => {
fn updatelist_to_html(update_lists: &TileCacheLoggerUpdateLists,
invalidation_report: String) -> String
{
let mut html = "\
<!DOCTYPE html>\n\
<html> <head> <meta charset=\"UTF-8\">\n\
<link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache_base.css\"></link>\n\
<link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache.css\"></link>\n\
</head> <body>\n\
<div class=\"datasheet\">\n".to_string();
html += &invalidation_report;
html += "<div class=\"header\">Interning</div>\n";
$(
html += &format!("<div class=\"subheader\">{}</div>\n<div class=\"intern data\">\n",
stringify!($name));
for list in &update_lists.$name.1 {
for insertion in &list.insertions {
html += &format!("<div class=\"insert\"><b>{}</b> {}</div>\n",
insertion.uid.get_uid(),
format!("({:?})", insertion.value));
}
for removal in &list.removals {
html += &format!("<div class=\"remove\"><b>{}</b></div>\n",
removal.uid.get_uid());
}
}
html += "</div><br/>\n";
)+
html += "</div> </body> </html>\n";
html
}
}
}
enumerate_interners!(updatelist_to_html_macro);
fn write_tile_cache_visualizer_svg(entry: &std::fs::DirEntry, output_dir: &Path,
slices: &[Slice], prev_slices: Option<Vec<Slice>>,
itemuid_to_string: &HashMap<ItemUid, String>,
svg_width: &mut i32, svg_height: &mut i32,
max_slice_index: &mut usize,
svg_files: &mut Vec::<String>,
svg_settings: &SvgSettings) -> String
{
let (svg, invalidation_report) = slices_to_svg(&slices, prev_slices,
itemuid_to_string,
svg_width, svg_height,
max_slice_index,
svg_settings);
let mut output_filename = OsString::from(entry.path().file_name().unwrap());
output_filename.push(".svg");
svg_files.push(output_filename.to_string_lossy().to_string());
output_filename = output_dir.join(output_filename).into_os_string();
let mut svg_output = File::create(output_filename).unwrap();
svg_output.write_all(svg.as_bytes()).unwrap();
invalidation_report
}
fn write_update_list_html(entry: &std::fs::DirEntry, output_dir: &Path,
update_lists: &TileCacheLoggerUpdateLists,
html_files: &mut Vec::<String>,
invalidation_report: String)
{
let html = updatelist_to_html(update_lists, invalidation_report);
let mut output_filename = OsString::from(entry.path().file_name().unwrap());
output_filename.push(".html");
html_files.push(output_filename.to_string_lossy().to_string());
output_filename = output_dir.join(output_filename).into_os_string();
let mut html_output = File::create(output_filename).unwrap();
html_output.write_all(html.as_bytes()).unwrap();
}
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
println!("Usage: tileview input_dir output_dir [scale [x y]]");
println!(" where input_dir is a tile_cache folder inside a wr-capture.");
println!(" Scale is an optional scaling factor to compensate for high-DPI.");
println!(" X, Y is an optional offset to shift the entire SVG by.");
println!("\nexample: cargo run c:/Users/me/AppData/Local/wr-capture.6/tile_cache/ c:/temp/tilecache/");
std::process::exit(1);
}
let input_dir = Path::new(&args[1]);
let output_dir = Path::new(&args[2]);
std::fs::create_dir_all(output_dir).unwrap();
let scale = if args.len() >= 4 { args[3].parse::<f32>().unwrap() } else { 1.0 };
let x = if args.len() >= 6 { args[4].parse::<f32>().unwrap() } else { 0.0 }; // >= 6, requires X and Y
let y = if args.len() >= 6 { args[5].parse::<f32>().unwrap() } else { 0.0 };
let svg_settings = SvgSettings { scale, x, y };
let mut svg_width = 100i32;
let mut svg_height = 100i32;
let mut max_slice_index = 0;
let mut entries: Vec<_> = std::fs::read_dir(input_dir).unwrap()
.filter_map(|r| r.ok())
.collect();
// auto-fix a missing 'tile_cache' postfix on the input path -- easy to do when copy-pasting a
// path to a wr-capture; there should at least be a frame00000.ron...
let frame00000 = entries.iter().find(|&entry| entry.path().ends_with("frame00000.ron"));
// ... and if not, try again with 'tile_cache' appended to the input folder
if frame00000.is_none() {
let new_path = input_dir.join("tile_cache");
entries = std::fs::read_dir(new_path).unwrap()
.filter_map(|r| r.ok())
.collect();
}
entries.sort_by_key(|dir| dir.path());
let mut svg_files: Vec::<String> = Vec::new();
let mut intern_files: Vec::<String> = Vec::new();
let mut prev_slices = None;
let mut itemuid_to_string = HashMap::default();
for entry in &entries {
if entry.path().is_dir() {
continue;
}
print!("processing {:?}\t", entry.path());
let file_data = std::fs::read_to_string(entry.path()).unwrap();
let chunks: Vec<_> = file_data.split("// @@@ chunk @@@").collect();
let slices: Vec<Slice> = match ron::de::from_str(&chunks[0]) {
Ok(data) => { data }
Err(e) => {
println!("ERROR: failed to deserialize slicesg {:?}\n{:?}", entry.path(), e);
prev_slices = None;
continue;
}
};
let mut update_lists = TileCacheLoggerUpdateLists::new();
update_lists.from_ron(&chunks[1]);
update_lists.insert_in_lookup(&mut itemuid_to_string);
let invalidation_report = write_tile_cache_visualizer_svg(
&entry, &output_dir,
&slices, prev_slices,
&itemuid_to_string,
&mut svg_width, &mut svg_height,
&mut max_slice_index,
&mut svg_files,
&svg_settings);
write_update_list_html(&entry, &output_dir, &update_lists,
&mut intern_files, invalidation_report);
print!("\r");
prev_slices = Some(slices);
}
write_html(output_dir, max_slice_index, &svg_files, &intern_files);
write_css(output_dir, max_slice_index, &svg_settings);
std::fs::write(output_dir.join("tilecache.js"), RES_JAVASCRIPT).unwrap();
std::fs::write(output_dir.join("tilecache_base.css"), RES_BASE_CSS).unwrap();
println!("\n");
}

View file

@ -1,187 +0,0 @@
// current SVG file for scrubbing and playback
var svg_index = 0;
// double buffered <object>s each holding an SVG file
var backbuffer;
var frontbuffer;
// timer for animation
var svg_timer;
var is_playing = false;
function toggle_play() {
if( is_playing ) {
is_playing = false;
clearInterval(svg_timer);
document.getElementById("text_spacebar").innerHTML =
'Spacebar to play';
} else {
is_playing = true;
svg_timer = setInterval(on_tick, 100);
document.getElementById("text_spacebar").innerHTML =
'Playing (Spacebar to stop)';
function on_tick() {
if( svg_index + 1 == svg_files.length ) {
toggle_play();
} else {
go_to_svg(svg_index+1);
}
}
}
}
function toggle_quadtree() {
var quad_groups = document.getElementsByClassName("svg_quadtree")
var i;
for (i = 0; i < quad_groups.length; i++) {
if( quad_groups[i].style.display == "none" )
quad_groups[i].style.display = "block";
else
quad_groups[i].style.display = "none";
}
}
function update_slice_visibility(max_slice) {
let content = frontbuffer.contentDocument;
update_slice_visibility_for_content(content, max_slice);
}
function update_slice_visibility_for_content(content, max_slice) {
intern = document.getElementById('intern').contentDocument;
for (let slice = 0; slice != max_slice; ++slice) {
var cbox_name = "slice_toggle" + slice;
let cbox = document.getElementById(cbox_name);
if( !cbox )
continue;
let checked = cbox.checked;
if (content) { // might fail due to cross scripting -- use SimpleHTTPServer
var id = "tile_slice" + slice + "_everything";
var group = content.getElementById(id);
if (group) {
if (checked)
group.style.display = "block";
else
group.style.display = "none";
}
}
if (intern) {
var id = "invalidation_slice" + slice;
var div = intern.getElementById(id);
if (div) {
if (checked)
div.style.display = "block";
else
div.style.display = "none";
}
}
}
}
// try to block repeated keypressed from causing flickering
// when they land between go_to_svg returning and onload
// firing.
var is_loading = false;
function go_to_svg(index) {
if( index >= svg_files.length ||
index < 0 ||
is_loading ) {
return;
}
is_loading = true;
svg_index = index;
var slider = document.getElementById('frame_slider');
// won't recurse thanks to is_loading
slider.value = svg_index;
backbuffer.onload = function(){
document.getElementById("text_frame_counter").innerHTML =
svg_files[svg_index];
let content = backbuffer.contentDocument;
update_slice_visibility_for_content(content, 20);
backbuffer.style.display = '';
frontbuffer.style.display = 'none';
var t = frontbuffer;
frontbuffer = backbuffer;
backbuffer = t;
is_loading = false;
}
document.getElementById('intern').src = intern_files[svg_index];
backbuffer.setAttribute('data', svg_files[svg_index]);
// also see https://stackoverflow.com/a/29915275
}
function load() {
window.addEventListener('keypress', handle_keyboard_shortcut);
window.addEventListener('keydown', handle_keydown);
frontbuffer = document.getElementById("svg_container0");
backbuffer = document.getElementById("svg_container1");
backbuffer.style.display='none';
var slider = document.getElementById('frame_slider');
slider.oninput = function() {
if( is_playing ) {
toggle_play();
}
go_to_svg(this.value);
}
}
function handle_keyboard_shortcut(event) {
switch (event.charCode) {
case 32: // ' '
toggle_play();
break;
case 113: // 'q'
toggle_quadtree();
break;
/*
case 49: // "1" key
document.getElementById("radio1").checked = true;
show_image(1);
break;
case 50: // "2" key
document.getElementById("radio2").checked = true;
show_image(2);
break;
case 100: // "d" key
document.getElementById("differences").click();
break;
case 112: // "p" key
shift_images(-1);
break;
case 110: // "n" key
shift_images(1);
break;
*/
}
}
function handle_keydown(event) {
switch (event.keyCode) {
case 37: // left arrow
go_to_svg(svg_index-1);
event.preventDefault();
break;
case 38: // up arrow
break;
case 39: // right arrow
go_to_svg(svg_index+1);
event.preventDefault();
break;
case 40: // down arrow
break;
}
}

View file

@ -1,109 +0,0 @@
.tile_svg {
float: left;
}
#intern {
position:relative;
top:60px;
width: 100%;
height: 100%;
color: orange;
border: 0px;
overflow: auto;
background: white;
}
.datasheet .header {
color: white;
font-family: Arial;
font-weight: bold;
font-size: 150%;
line-height: 200%;
background-color: grey;
margin-top: 15px;
margin-bottom: 5px;
padding-left: 10px;
}
.datasheet .subheader {
color: blue;
font-family: Arial;
font-weight: bold;
line-height: 200%;
background-color: lightgrey;
margin-top: 5px;
margin-bottom: 5px;
}
.datasheet .data {
font-family: monospace;
margin-bottom: 5px;
}
.datasheet .data *:nth-child(even) {
background: #FFFFFF;
}
.datasheet .data *:nth-child(odd) {
background: #EFEFEF;
}
.datasheet .data .insert {
color: #006000;
}
.datasheet .data .remove {
color: #600000;
}
.datasheet .data .change {
color: #000060;
}
.split {
position: fixed;
z-index: 1;
top: 0;
padding-top: 14px;
}
.left {
left: 0;
}
.right {
right: 0;
width: 30%;
height: 90%;
}
#svg_ui_overlay {
position:absolute;
right:0;
top:0;
z-index:70;
color: rgb(255,255,100);
font-family:monospace;
background-color: #404040a0;
}
#svg_ui_slider {
width:90%;
}
.svg_invalidated {
fill: white;
font-family:monospace;
}
.svg_quadtree {
fill: none;
stroke-width: 1;
stroke: orange;
}
.svg_changed_prim {
stroke: red;
stroke-width: 2.0;
}

View file

@ -1,72 +0,0 @@
[package]
name = "webrender"
version = "0.61.0"
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
license = "MPL-2.0"
repository = "https://github.com/servo/webrender"
description = "A GPU accelerated 2D renderer for web content"
build = "build.rs"
edition = "2018"
[features]
default = ["freetype-lib"]
freetype-lib = ["freetype/freetype-sys"]
profiler = ["tracy-rs/enable_profiler"]
capture = ["api/serialize", "ron", "serde", "smallvec/serde", "etagere/serialization"]
replay = ["api/deserialize", "ron", "serde", "smallvec/serde", "etagere/serialization"]
display_list_stats = ["api/display_list_stats"]
serialize_program = ["serde", "webrender_build/serialize_program"]
no_static_freetype = []
leak_checks = []
gecko = []
sw_compositor = ["swgl"]
[build-dependencies]
build-parallel = "0.1.2"
glslopt = "0.1.9"
webrender_build = { version = "0.0.1", path = "../webrender_build" }
[dependencies]
bincode = "1.0"
bitflags = "1.2"
byteorder = "1.0"
cstr = "0.2"
euclid = { version = "0.22.0", features = ["serde"] }
fxhash = "0.2.1"
gleam = "0.15"
lazy_static = "1"
log = "0.4"
malloc_size_of_derive = "0.1"
num-traits = "0.2"
plane-split = "0.17"
png = { optional = true, version = "0.16" }
rayon = "1"
ron = { optional = true, version = "0.8" }
serde = { optional = true, version = "1.0", features = ["serde_derive"] }
smallvec = "1"
time = "0.1"
api = { version = "0.61.0", path = "../webrender_api", package = "webrender_api" }
webrender_build = { version = "0.0.1", path = "../webrender_build" }
malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of", package = "wr_malloc_size_of" }
svg_fmt = "0.4"
tracy-rs = "0.1.2"
derive_more = "0.99"
etagere = "0.2.4"
swgl = { path = "../swgl", optional = true }
[dev-dependencies]
mozangle = "0.3.3"
rand = "0.4"
[target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
freetype = { version = "0.7", default-features = false }
libc = "0.2"
[target.'cfg(target_os = "windows")'.dependencies]
dwrote = "0.11"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9"
core-graphics = "0.22"
core-text = { version = "19", default-features = false }
objc = "0.2"

View file

@ -1,331 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::borrow::Cow;
use std::env;
use std::fs::{canonicalize, read_dir, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use webrender_build::shader::*;
use webrender_build::shader_features::{ShaderFeatureFlags, get_shader_features};
// glsopt is known to leak, but we don't particularly care.
#[no_mangle]
pub extern "C" fn __lsan_default_options() -> *const u8 {
b"detect_leaks=0\0".as_ptr()
}
/// Compute the shader path for insertion into the include_str!() macro.
/// This makes for more compact generated code than inserting the literal
/// shader source into the generated file.
///
/// If someone is building on a network share, I'm sorry.
fn escape_include_path(path: &Path) -> String {
let full_path = canonicalize(path).unwrap();
let full_name = full_path.as_os_str().to_str().unwrap();
let full_name = full_name.replace("\\\\?\\", "");
let full_name = full_name.replace("\\", "/");
full_name
}
fn write_unoptimized_shaders(mut glsl_files: Vec<PathBuf>, shader_file: &mut File) -> Result<(), std::io::Error> {
writeln!(
shader_file,
" pub static ref UNOPTIMIZED_SHADERS: HashMap<&'static str, SourceWithDigest> = {{"
)?;
writeln!(shader_file, " let mut shaders = HashMap::new();")?;
// Sort the file list so that the shaders.rs file is filled
// deterministically.
glsl_files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
for glsl in glsl_files {
// Compute the shader name.
assert!(glsl.is_file());
let shader_name = glsl.file_name().unwrap().to_str().unwrap();
let shader_name = shader_name.replace(".glsl", "");
// Compute a digest of the #include-expanded shader source. We store
// this as a literal alongside the source string so that we don't need
// to hash large strings at runtime.
let mut hasher = DefaultHasher::new();
let base = glsl.parent().unwrap();
assert!(base.is_dir());
ShaderSourceParser::new().parse(
Cow::Owned(shader_source_from_file(&glsl)),
&|f| Cow::Owned(shader_source_from_file(&base.join(&format!("{}.glsl", f)))),
&mut |s| hasher.write(s.as_bytes()),
);
let digest: ProgramSourceDigest = hasher.into();
writeln!(
shader_file,
" shaders.insert(\"{}\", SourceWithDigest {{ source: include_str!(\"{}\"), digest: \"{}\"}});",
shader_name,
escape_include_path(&glsl),
digest,
)?;
}
writeln!(shader_file, " shaders")?;
writeln!(shader_file, " }};")?;
Ok(())
}
#[derive(Clone, Debug)]
struct ShaderOptimizationInput {
shader_name: &'static str,
config: String,
gl_version: ShaderVersion,
}
#[derive(Debug)]
struct ShaderOptimizationOutput {
full_shader_name: String,
gl_version: ShaderVersion,
vert_file_path: PathBuf,
frag_file_path: PathBuf,
digest: ProgramSourceDigest,
}
#[derive(Debug)]
struct ShaderOptimizationError {
shader: ShaderOptimizationInput,
message: String,
}
fn print_shader_source(shader_src: &str) {
// For some reason the glsl-opt errors are offset by 1 compared
// to the provided shader source string.
println!("0\t|");
for (n, line) in shader_src.split('\n').enumerate() {
let line_number = n + 1;
println!("{}\t|{}", line_number, line);
}
}
fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &str) -> Result<(), std::io::Error> {
writeln!(
shader_file,
" pub static ref OPTIMIZED_SHADERS: HashMap<(ShaderVersion, &'static str), OptimizedSourceWithDigest> = {{"
)?;
writeln!(shader_file, " let mut shaders = HashMap::new();")?;
// The full set of optimized shaders can be quite large, so only optimize
// for the GL version we expect to be used on the target platform. If a different GL
// version is used we will simply fall back to the unoptimized shaders.
let shader_versions = match env::var("CARGO_CFG_TARGET_OS").as_ref().map(|s| &**s) {
Ok("android") | Ok("windows") => [ShaderVersion::Gles],
_ => [ShaderVersion::Gl],
};
let mut shaders = Vec::default();
for &gl_version in &shader_versions {
let mut flags = ShaderFeatureFlags::all();
if gl_version != ShaderVersion::Gl {
flags.remove(ShaderFeatureFlags::GL);
}
if gl_version != ShaderVersion::Gles {
flags.remove(ShaderFeatureFlags::GLES);
flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL);
}
if !matches!(env::var("CARGO_CFG_TARGET_OS").as_ref().map(|s| &**s), Ok("android")) {
flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL_ESSL1);
}
flags.remove(ShaderFeatureFlags::DITHERING);
for (shader_name, configs) in get_shader_features(flags) {
for config in configs {
shaders.push(ShaderOptimizationInput {
shader_name,
config,
gl_version,
});
}
}
}
let outputs = build_parallel::compile_objects(&|shader: &ShaderOptimizationInput| {
println!("Optimizing shader {:?}", shader);
let target = match shader.gl_version {
ShaderVersion::Gl => glslopt::Target::OpenGl,
ShaderVersion::Gles => glslopt::Target::OpenGles30,
};
let glslopt_ctx = glslopt::Context::new(target);
let features = shader.config.split(",").filter(|f| !f.is_empty()).collect::<Vec<_>>();
let (vert_src, frag_src) = build_shader_strings(
shader.gl_version,
&features,
shader.shader_name,
&|f| Cow::Owned(shader_source_from_file(&shader_dir.join(&format!("{}.glsl", f)))),
);
let full_shader_name = if shader.config.is_empty() {
shader.shader_name.to_string()
} else {
format!("{}_{}", shader.shader_name, shader.config.replace(",", "_"))
};
let vert = glslopt_ctx.optimize(glslopt::ShaderType::Vertex, vert_src.clone());
if !vert.get_status() {
print_shader_source(&vert_src);
return Err(ShaderOptimizationError {
shader: shader.clone(),
message: vert.get_log().to_string(),
});
}
let frag = glslopt_ctx.optimize(glslopt::ShaderType::Fragment, frag_src.clone());
if !frag.get_status() {
print_shader_source(&frag_src);
return Err(ShaderOptimizationError {
shader: shader.clone(),
message: frag.get_log().to_string(),
});
}
let vert_source = vert.get_output().unwrap();
let frag_source = frag.get_output().unwrap();
// Compute a digest of the optimized shader sources. We store this
// as a literal alongside the source string so that we don't need
// to hash large strings at runtime.
let mut hasher = DefaultHasher::new();
let vert_file_path = Path::new(out_dir)
.join(format!("{}_{:?}.vert", full_shader_name, shader.gl_version));
write_optimized_shader_file(&vert_file_path, vert_source, &shader.shader_name, &features, &mut hasher);
let frag_file_path = vert_file_path.with_extension("frag");
write_optimized_shader_file(&frag_file_path, frag_source, &shader.shader_name, &features, &mut hasher);
let digest: ProgramSourceDigest = hasher.into();
println!("Finished optimizing shader {:?}", shader);
Ok(ShaderOptimizationOutput {
full_shader_name,
gl_version: shader.gl_version,
vert_file_path,
frag_file_path,
digest,
})
}, &shaders);
match outputs {
Ok(mut outputs) => {
// Sort the shader list so that the shaders.rs file is filled
// deterministically.
outputs.sort_by(|a, b| {
(a.gl_version, a.full_shader_name.clone()).cmp(&(b.gl_version, b.full_shader_name.clone()))
});
for shader in outputs {
writeln!(
shader_file,
" shaders.insert(({}, \"{}\"), OptimizedSourceWithDigest {{",
shader.gl_version.variant_name(),
shader.full_shader_name,
)?;
writeln!(
shader_file,
" vert_source: include_str!(\"{}\"),",
escape_include_path(&shader.vert_file_path),
)?;
writeln!(
shader_file,
" frag_source: include_str!(\"{}\"),",
escape_include_path(&shader.frag_file_path),
)?;
writeln!(shader_file, " digest: \"{}\",", shader.digest)?;
writeln!(shader_file, " }});")?;
}
}
Err(err) => match err {
build_parallel::Error::BuildError(err) => {
panic!("Error optimizing shader {:?}: {}", err.shader, err.message)
}
_ => panic!("Error optimizing shaders."),
}
}
writeln!(shader_file, " shaders")?;
writeln!(shader_file, " }};")?;
Ok(())
}
fn write_optimized_shader_file(
path: &Path,
source: &str,
shader_name: &str,
features: &[&str],
hasher: &mut DefaultHasher,
) {
let mut file = File::create(&path).unwrap();
for (line_number, line) in source.lines().enumerate() {
// We embed the shader name and features as a comment in the
// source to make debugging easier.
// The #version directive must be on the first line so we insert
// the extra information on the next line.
if line_number == 1 {
let prelude = format!(
"// {}\n// features: {:?}\n\n",
shader_name, features
);
file.write_all(prelude.as_bytes()).unwrap();
hasher.write(prelude.as_bytes());
}
file.write_all(line.as_bytes()).unwrap();
file.write_all("\n".as_bytes()).unwrap();
hasher.write(line.as_bytes());
hasher.write("\n".as_bytes());
}
}
fn main() -> Result<(), std::io::Error> {
let out_dir = env::var("OUT_DIR").unwrap_or("out".to_owned());
let shaders_file_path = Path::new(&out_dir).join("shaders.rs");
let mut glsl_files = vec![];
println!("cargo:rerun-if-changed=res");
let res_dir = Path::new("res");
for entry in read_dir(res_dir)? {
let entry = entry?;
let path = entry.path();
if entry.file_name().to_str().unwrap().ends_with(".glsl") {
println!("cargo:rerun-if-changed={}", path.display());
glsl_files.push(path.to_owned());
}
}
let mut shader_file = File::create(shaders_file_path)?;
writeln!(shader_file, "/// AUTO GENERATED BY build.rs\n")?;
writeln!(shader_file, "use std::collections::HashMap;\n")?;
writeln!(shader_file, "use webrender_build::shader::ShaderVersion;\n")?;
writeln!(shader_file, "pub struct SourceWithDigest {{")?;
writeln!(shader_file, " pub source: &'static str,")?;
writeln!(shader_file, " pub digest: &'static str,")?;
writeln!(shader_file, "}}\n")?;
writeln!(shader_file, "pub struct OptimizedSourceWithDigest {{")?;
writeln!(shader_file, " pub vert_source: &'static str,")?;
writeln!(shader_file, " pub frag_source: &'static str,")?;
writeln!(shader_file, " pub digest: &'static str,")?;
writeln!(shader_file, "}}\n")?;
writeln!(shader_file, "lazy_static! {{")?;
write_unoptimized_shaders(glsl_files, &mut shader_file)?;
writeln!(shader_file, "")?;
write_optimized_shaders(&res_dir, &mut shader_file, &out_dir)?;
writeln!(shader_file, "}}")?;
Ok(())
}

View file

@ -1,150 +0,0 @@
# Original Design
To understand the current design for clipping and positioning (transformations
and scrolling) in WebRender it can be useful to have a little background about
the original design for these features. The most important thing to remember is
that originally clipping, scrolling regions, and transformations were
properties of stacking contexts and they were completely _hierarchical_. This
goes a long way toward representing the majority of CSS content on the web, but
fails when dealing with important edges cases and features including:
1. Support for sticky positioned content
2. Scrolling areas that include content that is ordered both above and below
intersecting content from outside the scroll area.
3. Items in the same scrolling root, clipped by different clips one or more of
which are defined outside the scrolling root itself.
4. Completely non-hierarchical clipping situations, such as when items are
clipped by some clips in the hierarchy, but not others.
Design changes have been a step by step path from the original design to one
that can handle all CSS content.
# Current Design
All positioning and clipping is handled by the `SpatialTree`. The name is a
holdover from when this tree was a tree of `Layers` which handled both
positioning and clipping. Currently the `SpatialTree` holds:
1. A hierarchical collection of `SpatialNodes`, with the final screen
transformation of each node depending on the relative transformation of the
node combined with the transformations of all of its ancestors. These nodes
are responsible for positioning display list items and clips.
2. A collection of `ClipNodes` which specify a rectangular clip and, optionally,
a set of rounded rectangle clips and a masking image.
3. A collection of `ClipChains`. Each `ClipChain` is a list of `ClipNode`
elements. Every display list item has an assigned `ClipChain` which
specifies what `ClipNodes` are applied to that item.
The `SpatialNode` of each clip applied to an item is completely independent of
the `SpatialNode` applied to the item itself.
One holdover from the previous design is that both `ClipNode` and `SpatialNodes`
have a parent node, which is either a `SpatialNode` or a `ClipNode`. From this
node WebRender can determine both a parent `ClipNode` and a parent `SpatialNode`
by finding the first ancestor of that type. This is handled by the
`DisplayListFlattener`.
## `SpatialNode`
There are three types of `SpatialNodes`:
1. Reference frames which are created when content needs to apply
transformation or perspective properties to display list items. Reference
frames establish a new coordinate system, so internally all coordinates on
display list items are relative to the reference frame origin. Later
any non-reference frame positioning nodes that display list items belong
to can adjust this position relative to the reference frame origin.
2. Scrolling nodes are used to define scrolling areas. These nodes have scroll
offsets which are a 2D translation relative to ancestor nodes and, ultimately,
the reference frame origin.
3. Sticky frames are responsible for implementing position:sticky behavior.
This is also an 2D translation.
`SpatialNodes` are defined as items in the display list. After scene building
each node is traversed hierarchically during the `SpatialTree::update()` step.
Once reference frame transforms and relative offsets are calculated, a to screen
space transformation can be calculated for each `SpatialNode`. This transformation
is added the `TransformPalette` and becomes directly available to WebRender shaders.
In addition to screen space transformation calculation, the `SpatialNode` tree
is divided up into _compatible coordinate systems_. These are coordinate systems
which differ only by 2D translations from their parent system. These compatible
coordinate systems may even cross reference frame boundaries. The goal here is
to allow the application clipping rectangles from different compatible
coordinate systems without generating mask images.
## `ClipNode`
Each clip node holds a clip rectangle along with an optional collection of
rounded clip rectangles and a mask image. The fact that `ClipNodes` all have a
clip rectangle is important because it means that all content clipped by a
clip node has a bounding rectangle, which can be converted into a bounding
screen space rectangle. This rectangle is called the _outer rectangle_ of the
clip. `ClipNodes` may also have an _inner rectangle_, which is an area within
the boundaries of the _outer rectangle_ that is completely unclipped.
These rectangles are calculated during the `SpatialTree::update()` phase. In
addition, each `ClipNode` produces a template `ClipChainNode` used to build
the `ClipChains` which use that node.
## `ClipChains`
There are two ways that `ClipChains` are defined in WebRender. The first is
through using the API for manually specifying `ClipChains` via a parent
`ClipChain` and a list of `ClipNodes`. The second is through the hierarchy of a
`ClipNode` established by its parent node. Every `ClipNode` has a chain of
ancestor `SpatialNodes` and `ClipNodes`. The creation of a `ClipNode`
automatically defines a `ClipChain` for this hierarchy. This behavior is a
compatibility feature with the old completely hierarchical clipping architecture
and is still how Gecko and Servo create most of their `ClipChains`. These
hierarchical `ClipChains` are constructed during the `ClipNode::update()` step.
During `ClipChain` construction, WebRender tries to eliminate clips that will
not affect rendering, by looking at the combined _outer rectangle_ and _inner
rectangle_ of a `ClipChain` and the _outer rectangle_ and _inner rectangle_ of
any `ClipNode` appended to the chain. An example of the goal of this process is
to avoid having to render a mask for a large rounded rectangle when the rest of
the clip chain constrains the content to an area completely inside that
rectangle. Avoiding mask rasterization in this case and others has large
performance impacts on WebRender.
# Clipping and Positioning in the Display List
Each non-structural WebRender display list item has
* A `SpatialId` of a `SpatialNode` for positioning
* A `ClipId` of a `ClipNode` or a `ClipChain` for clipping
* An item-specific rectangular clip rectangle
The positioning node determines how that item is positioned. It's assumed that
the positioning node and the item are children of the same reference frame. The
clipping node determines how that item is clipped. This should be fully
independent of how the node is positioned and items can be clipped by any
`ClipChain` regardless of the reference frame of their member clips. Finally,
the item-specific clipping rectangle is applied directly to the item and should
never result in the creation of a clip mask itself.
## Converting user-exposed `ClipId`/`SpatialId` to internal indices
WebRender must access `ClipNodes` and `SpatialNodes` quite a bit when building
scenes and frames, so it tries to convert `ClipId`/`SpatialId`, which are already
per-pipeline indices, to global scene-wide indices. Internally this is a
conversion from `ClipId` into `ClipNodeIndex` or `ClipChainIndex`, and from
`SpatialId` into `SpatialNodeIndex`. In order to make this conversion cheaper, the
`DisplayListFlattner` assigns offsets for each pipeline and node type in the
scene-wide `SpatialTree`.
Nodes are added to their respective arrays sequentially as the display list is
processed during scene building. When encountering an iframe, the
`DisplayListFlattener` must start processing the nodes for that iframe's
pipeline, meaning that nodes are now being added out of order to the node arrays
of the `SpatialTree`. In this case, the `SpatialTree` fills in the gaps in
the node arrays with placeholder nodes.
# Hit Testing
Hit testing is the responsibility of the `HitTester` data structure. This
structure copies information necessary for hit testing from the
`SpatialTree`. This is done so that hit testing can still take place while a
new `SpatialTree` is under construction.
# Ideas for the Future
1. Expose the difference between `ClipId` and `ClipChainId` in the API.
2. Prevent having to duplicate the `SpatialTree` for hit testing.
3. Avoid having to create placeholder nodes in the `SpatialTree` while
processing iframes.

View file

@ -1,43 +0,0 @@
# Blob images
Blob image is fallback mechanism for webrender that Gecko uses to render primitives that aren't currently supported by webrender. The main idea is to provide webrender with a custom handler that can take arbitray drawing commands serialized as buffers of bytes (the blobs) and turn them into images that webrender internally will treat as regular images.
At the API level, blob images are treated as other images. They are resources created and associated with image keys, and are used in the display list with regular image display items.
## Active area
In order to support scrolling very large content, blob images don't necessarily have a finite size. They can grow in any direction. At any time they do have an "active area", also called "visible area" which defines the portion that has to be rasterized. Typically this active area moves along large blob images depending on the scroll position.
The coordinate system of active area the *should* be the one of the blob's drawing commands (this is really up to the blob handler implementation to enforce that, Gecko does), and its scale should correspond to device pixels. The active area's coordinates can be negative.
As far as positioning goes, the active area maps to the image display item's bounds. In other words the content at the top-left corner of the active area will be rendered on screen at the position of the top-left corner of the display item's local rect.
In Gecko, the active area corresponds to the intersection of the fallback content's rect and the displayport.
The terms "visible area" and "visible rect" are used a lot in the blobs code, unfortunately they collide with frame building's visibility/culling terminology. They don't correspond to what is visible to the user, but rather what is in the displayport.
## Tiling
Blob images can be either tiled or non-tiled. Non-tiled blob images support invalid rects while tiled blob images track only validty at the tile level. In gecko all blobs are tiled with a tile size of 256x256.
Just like regular tiled images, blob image tiles along the border of the image are shrinked to fit the remaining size. The only difference is that the tiling pattern always starts at the top-left corner for regular images (smaller boundary tiles only along the right and bottom edges), while it can be aribtrarily positioned for blob images (smaller boundary tiles potentially on all sides).
The tiling logic is in webrender/src/image.rs.
## Async rasterization
Blobs are typically too slow to rasterize on the critical path. We try to avoid blocking frame building on blob image rasterization. In order to do that we rasterize blobs as part of scene building. Rather than rasterize tiles on demand from visibility informating, we rasterize the entire active area during scene building. This means we potentially process a lot more content than will be displayed if the user doesn't scroll through all of the visible area.
When the render backend receives a transaction, it looks for all new and update blob images, and generate blob rasterization requests for all tiles of the blob images that overlap their active area. The requests are bundled with an `AsyncBlobImageRasterizer` object in the transaction that is sent to the scene builder thread. The async rasterizer is created by the `BlobImageHandler` at each transaction. It is a snapshot of the state of the blobs as well as external information such as fonts, and does the actual rasterization.
While tiles are rasterized eagerly during scene building, their content is uploaded lazily to the texture cache depending on the result of the visibility pass during frame building.
## Late rasterization
In some case we run into a missing blob image during frame building and have to rasterize it synchronously. This happens when a rasterized tile is uploaded to the texture cache (at which point the CPU side is discarded), the texture cache entry expires and after scrolling back into view the tile is needed again.
We should really keep the rasterized blobs around just like we keep regular images in the cache. Hopefully this section will become obsolete eventually and we'll be able to remove late blob rasterization.
The information needed for async rasterization corresponds to the state of blobs before scene building while late rasterization needs the state of blobs after the last complete scene build. This means we have to be careful about which version we manipulate in the resource cache.

View file

@ -1,31 +0,0 @@
> It'd be great to have some (in-tree) docs describing the process you've worked through here, the overall motivation, how it works on different GPUs / platforms etc. Perhaps as a follow up?
# Swizzling in WR
## Problem statement
The goal is to avoid the CPU conversion of data done by the driver on texture data uploads. It's slow and always done synchronously, hurting our "Renderer" thread CPU utilization.
Gecko produces all of the image data in BGRA. Switching "imagelib" to RGBA is possible, but modifying Skia to follow is less trivial.
OpenGL support for BGRA8 as an internal texture format is a complex story: it's officially supported in Angle and a fair share of Android devices, but it's not available on the desktop GL (and until recently wasn't available in the Android emulator). Unofficially, when textures are initialized with `glTexImage` (as opposed to `glTexStorage`) with RGBA internal format, the desktop GL drivers often prefer to store the data in BGRA8 format, actually.
The only way to avoid the CPU conversion is to provide the data in exactly the same format that the driver is using internally for a texture. In this case, the driver does a straght `memcpy` into its CPU-visible memory, which is the best we can hope for with OpenGL API.
## Solution: swizzling
https://phabricator.services.mozilla.com/D21965 is providing the solution to this problem. The main principles are:
1. Use `glTexStorage` whenever it's available. Doing so gives us full control of the internal format, also allows to avoid allocating memory for mipmaps that we don't use.
2. Make the shared texture cache format to be determined at the init time, based on the GL device capabilities. For Angle and OpenGL ES this is BGRA8, for desktop this is RGBA8 (since desktop GL doesn't support BGRA internal formats). WebRender is now able to tell Gecko, which color format it prefers the texture data to use.
3. If the data comes in a format that is different from our best case, we pretend that the data is actually in our best case format, and associate the allocated cache entry with the `Swizzle`. That swizzle configuration changes the way shaders sample from a texture, adjusting for the fact the data was provided in a different format.
4. The lifetime of a "swizzled" texture cache data is starting at the point the data is uploaded and ending at a point where any shader samples from this data. Any other operation on that data (copying or blitting) is not configurable by `Swizzle` and thus would produce incorrect results. To address this, the change enhances `cs_copy` shader to be used in place of blitting from the texture cache, where needed.
5. Swizzling becomes a part of the batch key per texture. Mixing up draw calls with texture data that is differently swizzled then introduces the batch breaks. This is a downside for the swizzling approach in general, but it's not clear to what extent this would affect Gecko.
## Code paths
Windows/Angle and Android:
- we use `glTexStorage` with BGRA8 internal format, no swizzling is needed in general case.
Windows (non-Angle), Mac, Linux:
- if `glTexStorage` is available, we use it with RGBA8 internal format, swizzling everything on texture sampling.
- otherwise, we use RGBA unsized format with `gTexImage` and expect the data to come in BGRA format, no swizzling is involved.

View file

@ -1,720 +0,0 @@
# Text Rendering
This document describes the details of how WebRender renders text, particularly the blending stage of text rendering.
We will go into grayscale text blending, subpixel text blending, and "subpixel text with background color" blending.
### Prerequisites
The description below assumes you're familiar with regular rgba compositing, operator over,
and the concept of premultiplied alpha.
### Not covered in this document
We are going to treat the origin of the text mask as a black box.
We're also going to assume we can blend text in the device color space and will not go into the gamma correction and linear pre-blending that happens in some of the backends that produce the text masks.
## Grayscale Text Blending
Grayscale text blending is the simplest form of text blending. Our blending function has three inputs:
- The text color, as a premultiplied rgba color.
- The text mask, as a single-channel alpha texture.
- The existing contents of the framebuffer that we're rendering to, the "destination". This is also a premultiplied rgba buffer.
Note: The word "grayscale" here does *not* mean that we can only draw gray text.
It means that the mask only has a single alpha value per pixel, so we can visualize
the mask in our minds as a grayscale image.
### Deriving the math
We want to mask our text color using the single-channel mask, and composite that to the destination.
This compositing step uses operator "over", just like regular compositing of rgba images.
I'll be using GLSL syntax to describe the blend equations, but please consider most of the code below pseudocode.
We can express the blending described above as the following blend equation:
```glsl
vec4 textblend(vec4 text_color, vec4 mask, vec4 dest) {
return over(in(text_color, mask), dest);
}
```
with `over` being the blend function for (premultiplied) operator "over":
```glsl
vec4 over(vec4 src, vec4 dest) {
return src + (1.0 - src.a) * dest;
}
```
and `in` being the blend function for (premultiplied) operator "in", i.e. the masking operator:
```glsl
vec4 in(vec4 src, vec4 mask) {
return src * mask.a;
}
```
So the complete blending function is:
```glsl
result.r = text_color.r * mask.a + (1.0 - text_color.a * mask.a) * dest.r;
result.g = text_color.g * mask.a + (1.0 - text_color.a * mask.a) * dest.g;
result.b = text_color.b * mask.a + (1.0 - text_color.a * mask.a) * dest.b;
result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.a;
```
### Rendering this with OpenGL
In general, a fragment shader does not have access to the destination.
So the full blend equation needs to be expressed in a way that the shader only computes values that are independent of the destination,
and the parts of the equation that use the destination values need to be applied by the OpenGL blend pipeline itself.
The OpenGL blend pipeline can be tweaked using the functions `glBlendEquation` and `glBlendFunc`.
In our example, the fragment shader can output just `text_color * mask.a`:
```glsl
oFragColor = text_color * mask.a;
```
and the OpenGL blend pipeline can be configured like so:
```rust
pub fn set_blend_mode_premultiplied_alpha(&self) {
self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
self.gl.blend_equation(gl::FUNC_ADD);
}
```
This results in an overall blend equation of
```
result.r = 1 * oFragColor.r + (1 - oFragColor.a) * dest.r;
^ ^ ^^^^^^^^^^^^^^^^^
| | |
+--gl::ONE | +-- gl::ONE_MINUS_SRC_ALPHA
|
+-- gl::FUNC_ADD
= 1 * (text_color.r * mask.a) + (1 - (text_color.a * mask.a)) * dest.r
= text_color.r * mask.a + (1 - text_color.a * mask.a) * dest.r
```
which is exactly what we wanted.
### Differences to the actual WebRender code
There are two minor differences between the shader code above and the actual code in the text run shader in WebRender:
```glsl
oFragColor = text_color * mask.a; // (shown above)
// vs.
oFragColor = vColor * mask * alpha; // (actual webrender code)
```
`vColor` is set to the text color. The differences are:
- WebRender multiplies with all components of `mask` instead of just with `mask.a`.
However, our font rasterization code fills the rgb values of `mask` with the value of `mask.a`,
so this is completely equivalent.
- WebRender applies another alpha to the text. This is coming from the clip.
You can think of this alpha to be a pre-adjustment of the text color for that pixel, or as an
additional mask that gets applied to the mask.
## Subpixel Text Blending
Now that we have the blend equation for single-channel text blending, we can look at subpixel text blending.
The main difference between subpixel text blending and grayscale text blending is the fact that,
for subpixel text, the text mask contains a separate alpha value for each color component.
### Component alpha
Regular painting uses four values per pixel: three color values, and one alpha value. The alpha value applies to all components of the pixel equally.
Imagine for a second a world in which you have *three alpha values per pixel*, one for each color component.
- Old world: Each pixel has four values: `color.r`, `color.g`, `color.b`, and `color.a`.
- New world: Each pixel has *six* values: `color.r`, `color.a_r`, `color.g`, `color.a_g`, `color.b`, and `color.a_b`.
In such a world we can define a component-alpha-aware operator "over":
```glsl
vec6 over_comp(vec6 src, vec6 dest) {
vec6 result;
result.r = src.r + (1.0 - src.a_r) * dest.r;
result.g = src.g + (1.0 - src.a_g) * dest.g;
result.b = src.b + (1.0 - src.a_b) * dest.b;
result.a_r = src.a_r + (1.0 - src.a_r) * dest.a_r;
result.a_g = src.a_g + (1.0 - src.a_g) * dest.a_g;
result.a_b = src.a_b + (1.0 - src.a_b) * dest.a_b;
return result;
}
```
and a component-alpha-aware operator "in":
```glsl
vec6 in_comp(vec6 src, vec6 mask) {
vec6 result;
result.r = src.r * mask.a_r;
result.g = src.g * mask.a_g;
result.b = src.b * mask.a_b;
result.a_r = src.a_r * mask.a_r;
result.a_g = src.a_g * mask.a_g;
result.a_b = src.a_b * mask.a_b;
return result;
}
```
and even a component-alpha-aware version of `textblend`:
```glsl
vec6 textblend_comp(vec6 text_color, vec6 mask, vec6 dest) {
return over_comp(in_comp(text_color, mask), dest);
}
```
This results in the following set of equations:
```glsl
result.r = text_color.r * mask.a_r + (1.0 - text_color.a_r * mask.a_r) * dest.r;
result.g = text_color.g * mask.a_g + (1.0 - text_color.a_g * mask.a_g) * dest.g;
result.b = text_color.b * mask.a_b + (1.0 - text_color.a_b * mask.a_b) * dest.b;
result.a_r = text_color.a_r * mask.a_r + (1.0 - text_color.a_r * mask.a_r) * dest.a_r;
result.a_g = text_color.a_g * mask.a_g + (1.0 - text_color.a_g * mask.a_g) * dest.a_g;
result.a_b = text_color.a_b * mask.a_b + (1.0 - text_color.a_b * mask.a_b) * dest.a_b;
```
### Back to the real world
If we want to transfer the component alpha blend equation into the real world, we need to make a few small changes:
- Our text color only needs one alpha value.
So we'll replace all instances of `text_color.a_r/g/b` with `text_color.a`.
- We're currently not making use of the mask's `r`, `g` and `b` values, only of the `a_r`, `a_g` and `a_b` values.
So in the real world, we can use the rgb channels of `mask` to store those component alphas and
replace `mask.a_r/g/b` with `mask.r/g/b`.
These two changes give us:
```glsl
result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r;
result.g = text_color.g * mask.g + (1.0 - text_color.a * mask.g) * dest.g;
result.b = text_color.b * mask.b + (1.0 - text_color.a * mask.b) * dest.b;
result.a_r = text_color.a * mask.r + (1.0 - text_color.a * mask.r) * dest.a_r;
result.a_g = text_color.a * mask.g + (1.0 - text_color.a * mask.g) * dest.a_g;
result.a_b = text_color.a * mask.b + (1.0 - text_color.a * mask.b) * dest.a_b;
```
There's a third change we need to make:
- We're rendering to a destination surface that only has one alpha channel instead of three.
So `dest.a_r/g/b` and `result.a_r/g/b` will need to become `dest.a` and `result.a`.
This creates a problem: We're currently assigning different values to `result.a_r`, `result.a_g` and `result.a_b`.
Which of them should we use to compute `result.a`?
This question does not have an answer. One alpha value per pixel is simply not sufficient
to express the same information as three alpha values.
However, see what happens if the destination is already opaque:
We have `dest.a_r == 1`, `dest.a_g == 1`, and `dest.a_b == 1`.
```
result.a_r = text_color.a * mask.r + (1 - text_color.a * mask.r) * dest.a_r
= text_color.a * mask.r + (1 - text_color.a * mask.r) * 1
= text_color.a * mask.r + 1 - text_color.a * mask.r
= 1
same for result.a_g and result.a_b
```
In other words, for opaque destinations, it doesn't matter what which channel of the mask we use when computing `result.a`, the result will always be completely opaque anyways. In WebRender we just pick `mask.g` (or rather,
have font rasterization set `mask.a` to the value of `mask.g`) because it's as good as any.
The takeaway here is: **Subpixel text blending is only supported for opaque destinations.** Attempting to render subpixel
text into partially transparent destinations will result in bad alpha values. Or rather, it will result in alpha values which
are not anticipated by the r, g, and b values in the same pixel, so that subsequent blend operations, which will mix r and a values
from the same pixel, will produce incorrect colors.
Here's the final subpixel blend function:
```glsl
vec4 subpixeltextblend(vec4 text_color, vec4 mask, vec4 dest) {
vec4 result;
result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r;
result.g = text_color.g * mask.g + (1.0 - text_color.a * mask.g) * dest.g;
result.b = text_color.b * mask.b + (1.0 - text_color.a * mask.b) * dest.b;
result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.a;
return result;
}
```
or for short:
```glsl
vec4 subpixeltextblend(vec4 text_color, vec4 mask, vec4 dest) {
return text_color * mask + (1.0 - text_color.a * mask) * dest;
}
```
To recap, here's what we gained and lost by making the transition from the full-component-alpha world to the
regular rgba world: All colors and textures now only need four values to be represented, we still use a
component alpha mask, and the results are equivalent to the full-component-alpha result assuming that the
destination is opaque. We lost the ability to draw to partially transparent destinations.
### Making this work in OpenGL
We have the complete subpixel blend function.
Now we need to cut it into pieces and mix it with the OpenGL blend pipeline in such a way that
the fragment shader does not need to know about the destination.
Compare the equation for the red channel and the alpha channel between the two ways of text blending:
```
single-channel alpha:
result.r = text_color.r * mask.a + (1.0 - text_color.a * mask.a) * dest.r
result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.r
component alpha:
result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r
result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.r
```
Notably, in the single-channel alpha case, all three destination color channels are multiplied with the same thing:
`(1.0 - text_color.a * mask.a)`. This factor also happens to be "one minus `oFragColor.a`".
So we were able to take advantage of OpenGL's `ONE_MINUS_SRC_ALPHA` blend func.
In the component alpha case, we're not so lucky: Each destination color channel
is multiplied with a different factor. We can use `ONE_MINUS_SRC_COLOR` instead,
and output `text_color.a * mask` from our fragment shader.
But then there's still the problem that the first summand of the computation for `result.r` uses
`text_color.r * mask.r` and the second summand uses `text_color.a * mask.r`.
There are multiple ways to deal with this. They are:
1. Making use of `glBlendColor` and the `GL_CONSTANT_COLOR` blend func.
2. Using a two-pass method.
3. Using "dual source blending".
Let's look at them in order.
#### 1. Subpixel text blending in OpenGL using `glBlendColor`
In this approach we return `text_color.a * mask` from the shader.
Then we set the blend color to `text_color / text_color.a` and use `GL_CONSTANT_COLOR` as the source blendfunc.
This results in the following blend equation:
```
result.r = (text_color.r / text_color.a) * oFragColor.r + (1 - oFragColor.r) * dest.r;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^
| | |
+--gl::CONSTANT_COLOR | +-- gl::ONE_MINUS_SRC_COLOR
|
+-- gl::FUNC_ADD
= (text_color.r / text_color.a) * (text_color.a * mask.r) + (1 - (text_color.a * mask.r)) * dest.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r
```
At the very beginning of this document, we defined `text_color` as the *premultiplied* text color.
So instead of actually doing the calculation `text_color.r / text_color.a` when specifying the blend color,
we really just want to use the *unpremultiplied* text color in that place.
That's usually the representation we start with anyway.
#### 2. Two-pass subpixel blending in OpenGL
The `glBlendColor` method has the disadvantage that the text color is part of the OpenGL state.
So if we want to draw text with different colors, we have two use separate batches / draw calls
to draw the differently-colored parts of text.
Alternatively, we can use a two-pass method which avoids the need to use the `GL_CONSTANT_COLOR` blend func:
- The first pass outputs `text_color.a * mask` from the fragment shader and
uses `gl::ZERO, gl::ONE_MINUS_SRC_COLOR` as the glBlendFuncs. This achieves:
```
oFragColor = text_color.a * mask;
result_after_pass0.r = 0 * oFragColor.r + (1 - oFragColor.r) * dest.r
= (1 - text_color.a * mask.r) * dest.r
result_after_pass0.g = 0 * oFragColor.g + (1 - oFragColor.g) * dest.r
= (1 - text_color.a * mask.r) * dest.r
...
```
- The second pass outputs `text_color * mask` from the fragment shader and uses
`gl::ONE, gl::ONE` as the glBlendFuncs. This results in the correct overall blend equation.
```
oFragColor = text_color * mask;
result_after_pass1.r
= 1 * oFragColor.r + 1 * result_after_pass0.r
= text_color.r * mask.r + result_after_pass0.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r
```
#### 3. Dual source subpixel blending in OpenGL
The third approach is similar to the second approach, but makes use of the [`ARB_blend_func_extended`](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_blend_func_extended.txt) extension
in order to fold the two passes into one:
Instead of outputting the two different colors in two separate passes, we output them from the same pass,
as two separate fragment shader outputs.
Those outputs can then be treated as two different sources in the blend equation.
## Subpixel Text Rendering to Transparent Destinations with a Background Color Hint
### Motivation
As we've seen in the previous section, subpixel text drawing has the limitation that it only works on opaque destinations.
In other words, if you use the `subpixeltextblend` function to draw something to a transparent surface,
and then composite that surface onto on opaque background,
the result will generally be different from drawing the text directly onto the opaque background.
Let's express that inequality in code.
```
- vec4 text_color
- vec4 mask
- vec4 transparency = vec4(0.0, 0.0, 0.0, 0.0)
- vec4 background with background.a == 1.0
over(subpixeltextblend(text_color, mask, transparency), background).rgb
is, in general, not equal to
subpixeltextblend(text_color, mask, background).rgb
```
However, one interesting observation is that if the background is black, the two *are* equal:
```
vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
over(subpixeltextblend(text_color, mask, transparency), black).r
= subpixeltextblend(text_color, mask, transparency).r +
(1 - subpixeltextblend(text_color, mask, transparency).a) * black.r
= subpixeltextblend(text_color, mask, transparency).r +
(1 - subpixeltextblend(text_color, mask, transparency).a) * 0
= subpixeltextblend(text_color, mask, transparency).r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * transparency.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * 0
= text_color.r * mask.r + (1 - text_color.a * mask.r) * black.r
= subpixeltextblend(text_color, mask, black).r
```
So it works out for black backgrounds. The further your *actual* background color gets away from black,
the more incorrect your result will be.
If it works for black, is there a way to make it work for other colors?
This is the motivating question for this third way of text blending:
We want to be able to specify an *estimated background color*, and have a blending function
`vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest)`,
in such a way that the error we get by using an intermediate surface is somehow in relation
to the error we made when estimating the background color. In particular, if we estimated
the background color perfectly, we want the intermediate surface to go unnoticed.
Expressed as code:
```
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency), bg_color)
should always be equal to
subpixeltextblend(text_color, mask, bg_color)
```
This is one of three constraints we'd like `subpixeltextblend_withbgcolor` to satisfy.
The next constraint is the following: If `dest` is already opaque, `subpixeltextblend_withbgcolor`
should have the same results as `subpixeltextblend`, and the background color hint should be ignored.
```
If dest.a == 1.0,
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest)
should always be equal to
subpixeltextblend(text_color, mask, dest)
```
And there's a third condition we'd like it to fulfill:
In places where the mask is zero, the destination should be unaffected.
```
subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest)
should always be equal to
dest
```
### Use cases
The primary use case for such a blend method is text on top of vibrant areas of a window on macOS.
Vibrant backgrounds with behind-window blending are computed by the window server, and they are tinted
in a color that's based on the chosen vibrancy type.
The window's rgba buffer is transparent in the vibrant areas. Window contents, even text, are drawn onto
that transparent rgba buffer. Then the window server composites the window onto an opaque backdrop.
So the results on the screen are computed as follows:
```glsl
window_buffer_pixel = subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency);
screen_pixel = over(window_buffer_pixel, window_backdrop);
```
### Prior art
Apple has implemented such a method of text blending in CoreGraphics, specifically for rendering text onto vibrant backgrounds.
It's hidden behind the private API `CGContextSetFontSmoothingBackgroundColor` and is called by AppKit internally before
calling the `-[NSView drawRect:]` method of your `NSVisualEffectView`, with the appropriate font smoothing background color
for the vibrancy type of that view.
I'm not aware of any public documentation of this way of text blending.
It seems to be considered an implementation detail by Apple, and is probably hidden by default because it can be a footgun:
If the font smoothing background color you specify is very different from the actual background that our surface is placed
on top of, the text will look glitchy.
### Deriving the blending function from first principles
Before we dive into the math, let's repeat our goal once more.
We want to create a blending function of the form
`vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest)`
(with `bg_color` being an opaque color)
which satisfies the following three constraints:
```
Constraint I:
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency), bg_color)
should always be equal to
subpixeltextblend(text_color, mask, bg_color)
Constraint II:
If dest.a == 1.0,
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest)
should always be equal to
subpixeltextblend(text_color, mask, dest)
Constraint II:
subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest)
should always be equal to
dest
```
Constraint I and constraint II are about what happens depending on the destination's alpha.
In particular: If the destination is completely transparent, we should blend into the
estimated background color, and if it's completely opaque, we should blend into the destination color.
In fact, we really want to blend into `over(dest, bg_color)`: we want `bg_color` to be used
as a backdrop *behind* the current destination. So let's combine constraints I and II into a new
constraint IV:
```
Constraint IV:
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color)
should always be equal to
subpixeltextblend(text_color, mask, over(dest, bg_color))
```
Let's look at just the left side of that equation and rejiggle it a bit:
```
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r
= subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r +
(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
<=>
over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r -
(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
= subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
```
Now insert the right side of constraint IV:
```
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
= over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r -
(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
= subpixeltextblend(text_color, mask, over(dest, bg_color)).r -
(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
```
Our blend function is almost finished. We just need select an alpha for our result.
Constraints I, II and IV don't really care about the alpha value. But constraint III requires that:
```
subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest).a
should always be equal to
dest.a
```
so the computation of the alpha value somehow needs to take into account the mask.
Let's say we have an unknown function `make_alpha(text_color.a, mask)` which returns
a number between 0 and 1 and which is 0 if the mask is entirely zero, and let's defer
the actual implementation of that function until later.
Now we can define the alpha of our overall function using the `over` function:
```
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a
:= make_alpha(text_color.a, mask) + (1 - make_alpha(text_color.a, mask)) * dest.a
```
We can plug this in to our previous result:
```
subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
= subpixeltextblend(text_color, mask, over(dest, bg_color)).r
- (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
= subpixeltextblend(text_color, mask, over(dest, bg_color)).r
- (1 - (make_alpha(text_color.a, mask) +
(1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * over(dest, bg_color).r
- (1 - (make_alpha(text_color.a, mask)
+ (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
- (1 - (make_alpha(text_color.a, mask)
+ (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
- (1 - (make_alpha(text_color.a, mask)
+ (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
= text_color.r * mask.r
+ (dest.r + (1 - dest.a) * bg_color.r)
- (text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
- (1 - make_alpha(text_color.a, mask)
- (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
= text_color.r * mask.r
+ dest.r + (1 - dest.a) * bg_color.r
- text_color.a * mask.r * dest.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- (1 - make_alpha(text_color.a, mask)
- (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
= text_color.r * mask.r
+ dest.r + (1 - dest.a) * bg_color.r
- text_color.a * mask.r * dest.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- ((1 - make_alpha(text_color.a, mask)) * 1
- (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
= text_color.r * mask.r
+ dest.r + (1 - dest.a) * bg_color.r
- text_color.a * mask.r * dest.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- ((1 - make_alpha(text_color.a, mask)) * (1 - dest.a)) * bg_color.r
= text_color.r * mask.r
+ dest.r - text_color.a * mask.r * dest.r
+ (1 - dest.a) * bg_color.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ (1 - dest.a) * bg_color.r
- text_color.a * mask.r * (1 - dest.a) * bg_color.r
- (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ (1 - text_color.a * mask.r) * (1 - dest.a) * bg_color.r
- (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ ((1 - text_color.a * mask.r)
- (1 - make_alpha(text_color.a, mask))) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ (1 - text_color.a * mask.r
- 1 + make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
= text_color.r * mask.r
+ (1 - text_color.a * mask.r) * dest.r
+ (make_alpha(text_color.a, mask) - text_color.a * mask.r) * (1 - dest.a) * bg_color.r
```
We now have a term of the form `A + B + C`, with `A` and `B` being guaranteed to
be between zero and one.
We also want `C` to be between zero and one.
We can use this restriction to help us decide on an implementation of `make_alpha`.
If we define `make_alpha` as
```glsl
float make_alpha(text_color_a, mask) {
float max_rgb = max(max(mask.r, mask.g), mask.b);
return text_color_a * max_rgb;
}
```
, then `(make_alpha(text_color.a, mask) - text_color.a * mask.r)` becomes
`(text_color.a * max(max(mask.r, mask.g), mask.b) - text_color.a * mask.r)`, which is
`text_color.a * (max(max(mask.r, mask.g), mask.b) - mask.r)`, and the subtraction will
always yield something that's greater or equal to zero for r, g, and b,
because we will subtract each channel from the maximum of the channels.
Putting this all together, we have:
```glsl
vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest) {
float max_rgb = max(max(mask.r, mask.g), mask.b);
vec4 result;
result.r = text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r +
text_color.a * bg_color.r * (max_rgb - mask.r) * (1 - dest.a);
result.g = text_color.g * mask.g + (1 - text_color.a * mask.g) * dest.g +
text_color.a * bg_color.g * (max_rgb - mask.g) * (1 - dest.a);
result.b = text_color.b * mask.b + (1 - text_color.a * mask.b) * dest.b +
text_color.a * bg_color.b * (max_rgb - mask.b) * (1 - dest.a);
result.a = text_color.a * max_rgb + (1 - text_color.a * max_rgb) * dest.a;
return result;
}
```
This is the final form of this blend function. It satisfies all of the four constraints.
### Implementing it with OpenGL
Our color channel equations consist of three pieces:
- `text_color.r * mask.r`, which simply gets added to the rest.
- `(1 - text_color.a * mask.r) * dest.r`, a factor which gets multiplied with the destination color.
- `text_color.a * bg_color.r * (max_rgb - mask.r) * (1 - dest.a)`, a factor which gets multiplied
with "one minus destination alpha".
We will need three passes. Each pass modifies the color channels in the destination.
This means that the part that uses `dest.r` needs to be applied first.
Then we can apply the part that uses `1 - dest.a`.
(This means that the first pass needs to leave `dest.a` untouched.)
And the final pass can apply the `result.a` equation and modify `dest.a`.
```
pub fn set_blend_mode_subpixel_with_bg_color_pass0(&self) {
self.gl.blend_func_separate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
}
pub fn set_blend_mode_subpixel_with_bg_color_pass1(&self) {
self.gl.blend_func_separate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
}
pub fn set_blend_mode_subpixel_with_bg_color_pass2(&self) {
self.gl.blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
}
Pass0:
oFragColor = vec4(text.color.a) * mask;
Pass1:
oFragColor = vec4(text.color.a) * text.bg_color * (vec4(mask.a) - mask);
Pass2:
oFragColor = text.color * mask;
result_after_pass0.r = 0 * (text_color.a * mask.r) + (1 - text_color.a * mask.r) * dest.r
result_after_pass0.a = 0 * (text_color.a * mask.a) + 1 * dest.a
result_after_pass1.r = (1 - result_after_pass0.a) * (text_color.a * (mask.max_rgb - mask.r) * bg_color.r) + 1 * result_after_pass0.r
result_after_pass1.a = 0 * (text_color.a * (mask.max_rgb - mask.a) * bg_color.a) + 1 * result_after_pass0.a
result_after_pass2.r = 1 * (text_color.r * mask.r) + 1 * result_after_pass1.r
result_after_pass2.a = 1 * (text_color.a * mask.max_rgb) + (1 - text_color.a * mask.max_rgb) * result_after_pass1.a
```
Instead of computing `max_rgb` in the shader, we can just require the font rasterization code to fill
`mask.a` with the `max_rgb` value.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more