1807 lines
84 KiB
HTML
1807 lines
84 KiB
HTML
|
<html>
|
|||
|
<head>
|
|||
|
<meta charset="utf-8" />
|
|||
|
<title>Latency mitigation strategies (by John Carmack)</title>
|
|||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|||
|
<style>
|
|||
|
img {
|
|||
|
max-width: 100%;
|
|||
|
height: auto;
|
|||
|
}
|
|||
|
pre {
|
|||
|
max-width: 100%;
|
|||
|
height: auto;
|
|||
|
white-space: pre-wrap;
|
|||
|
}
|
|||
|
.np {
|
|||
|
display: flex;
|
|||
|
flex-direction: row;
|
|||
|
justify-content: space-between;
|
|||
|
padding-bottom: 0.5em;
|
|||
|
font-style: italic;
|
|||
|
}
|
|||
|
</style>
|
|||
|
<link rel="icon" href="data:;base64,=" />
|
|||
|
<style type="text/css">
|
|||
|
@font-face {
|
|||
|
font-weight: 400;
|
|||
|
font-style: normal;
|
|||
|
font-family: 'Circular-Loom';
|
|||
|
|
|||
|
src: url('https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Book-cd7d2bcec649b1243839a15d5eb8f0a3.woff2')
|
|||
|
format('woff2');
|
|||
|
}
|
|||
|
|
|||
|
@font-face {
|
|||
|
font-weight: 500;
|
|||
|
font-style: normal;
|
|||
|
font-family: 'Circular-Loom';
|
|||
|
|
|||
|
src: url('https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Medium-d74eac43c78bd5852478998ce63dceb3.woff2')
|
|||
|
format('woff2');
|
|||
|
}
|
|||
|
|
|||
|
@font-face {
|
|||
|
font-weight: 700;
|
|||
|
font-style: normal;
|
|||
|
font-family: 'Circular-Loom';
|
|||
|
|
|||
|
src: url('https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Bold-83b8ceaf77f49c7cffa44107561909e4.woff2')
|
|||
|
format('woff2');
|
|||
|
}
|
|||
|
|
|||
|
@font-face {
|
|||
|
font-weight: 900;
|
|||
|
font-style: normal;
|
|||
|
font-family: 'Circular-Loom';
|
|||
|
|
|||
|
src: url('https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Black-bf067ecb8aa777ceb6df7d72226febca.woff2')
|
|||
|
format('woff2');
|
|||
|
}
|
|||
|
</style>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<strong>Latency mitigation strategies (by John Carmack)</strong>
|
|||
|
<p>
|
|||
|
<i
|
|||
|
>this is an archive of an old article by John Carmack which seems to
|
|||
|
have disappeared off of the internet</i
|
|||
|
>
|
|||
|
</p>
|
|||
|
<h4 id="abstract">Abstract</h4>
|
|||
|
<p>
|
|||
|
Virtual reality (VR) is one of the most demanding human-in-the-loop
|
|||
|
applications from a latency standpoint. The latency between the physical
|
|||
|
movement of a user’s head and updated photons from a head mounted display
|
|||
|
reaching their eyes is one of the most critical factors in providing a
|
|||
|
high quality experience.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Human sensory systems can detect very small relative delays in parts of
|
|||
|
the visual or, especially, audio fields, but when absolute delays are
|
|||
|
below approximately 20 milliseconds they are generally imperceptible.
|
|||
|
Interactive 3D systems today typically have latencies that are several
|
|||
|
times that figure, but alternate configurations of the same hardware
|
|||
|
components can allow that target to be reached.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
A discussion of the sources of latency throughout a system follows, along
|
|||
|
with techniques for reducing the latency in the processing done on the
|
|||
|
host system.
|
|||
|
</p>
|
|||
|
<h4 id="introduction">Introduction</h4>
|
|||
|
<p>
|
|||
|
Updating the imagery in a head mounted display (HMD) based on a head
|
|||
|
tracking sensor is a subtly different challenge than most human / computer
|
|||
|
interactions. With a conventional mouse or game controller, the user is
|
|||
|
consciously manipulating an interface to complete a task, while the goal
|
|||
|
of virtual reality is to have the experience accepted at an unconscious
|
|||
|
level.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Users can adapt to control systems with a significant amount of latency
|
|||
|
and still perform challenging tasks or enjoy a game; many thousands of
|
|||
|
people enjoyed playing early network games, even with 400+ milliseconds of
|
|||
|
latency between pressing a key and seeing a response on screen.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
If large amounts of latency are present in the VR system, users may still
|
|||
|
be able to perform tasks, but it will be by the much less rewarding means
|
|||
|
of using their head as a controller, rather than accepting that their head
|
|||
|
is naturally moving around in a stable virtual world. Perceiving latency
|
|||
|
in the response to head motion is also one of the primary causes of
|
|||
|
simulator sickness. Other technical factors that affect the quality of a
|
|||
|
VR experience, like head tracking accuracy and precision, may interact
|
|||
|
with the perception of latency, or, like display resolution and color
|
|||
|
depth, be largely orthogonal to it.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
A total system latency of 50 milliseconds will feel responsive, but still
|
|||
|
subtly lagging. One of the easiest ways to see the effects of latency in a
|
|||
|
head mounted display is to roll your head side to side along the view
|
|||
|
vector while looking at a clear vertical edge. Latency will show up as an
|
|||
|
apparent tilting of the vertical line with the head motion; the view feels
|
|||
|
“dragged along” with the head motion. When the latency is low enough, the
|
|||
|
virtual world convincingly feels like you are simply rotating your view of
|
|||
|
a stable world.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Extrapolation of sensor data can be used to mitigate some system latency,
|
|||
|
but even with a sophisticated model of the motion of the human head, there
|
|||
|
will be artifacts as movements are initiated and changed. It is always
|
|||
|
better to not have a problem than to mitigate it, so true latency
|
|||
|
reduction should be aggressively pursued, leaving extrapolation to smooth
|
|||
|
out sensor jitter issues and perform only a small amount of prediction.
|
|||
|
</p>
|
|||
|
<h4 id="data-collection">Data collection</h4>
|
|||
|
<p>
|
|||
|
It is not usually possible to introspectively measure the complete system
|
|||
|
latency of a VR system, because the sensors and display devices external
|
|||
|
to the host processor make significant contributions to the total latency.
|
|||
|
An effective technique is to record high speed video that simultaneously
|
|||
|
captures the initiating physical motion and the eventual display update.
|
|||
|
The system latency can then be determined by single stepping the video and
|
|||
|
counting the number of video frames between the two events.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
In most cases there will be a significant jitter in the resulting timings
|
|||
|
due to aliasing between sensor rates, display rates, and camera rates, but
|
|||
|
conventional applications tend to display total latencies in the dozens of
|
|||
|
240 fps video frames.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
On an unloaded Windows 7 system with the compositing Aero desktop
|
|||
|
interface disabled, a gaming mouse dragging a window displayed on a 180 hz
|
|||
|
CRT monitor can show a response on screen in the same 240 fps video frame
|
|||
|
that the mouse was seen to first move, demonstrating an end to end latency
|
|||
|
below four milliseconds. Many systems need to cooperate for this to
|
|||
|
happen: The mouse updates 500 times a second, with no filtering or
|
|||
|
buffering. The operating system immediately processes the update, and
|
|||
|
immediately performs GPU accelerated rendering directly to the framebuffer
|
|||
|
without any page flipping or buffering. The display accepts the video
|
|||
|
signal with no buffering or processing, and the screen phosphors begin
|
|||
|
emitting new photons within microseconds.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
In a typical VR system, many things go far less optimally, sometimes
|
|||
|
resulting in end to end latencies of over 100 milliseconds.
|
|||
|
</p>
|
|||
|
<h4 id="sensors">Sensors</h4>
|
|||
|
<p>
|
|||
|
Detecting a physical action can be as simple as a watching a circuit close
|
|||
|
for a button press, or as complex as analyzing a live video feed to infer
|
|||
|
position and orientation.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
In the old days, executing an IO port input instruction could directly
|
|||
|
trigger an analog to digital conversion on an ISA bus adapter card, giving
|
|||
|
a latency on the order of a microsecond and no sampling jitter issues.
|
|||
|
Today, sensors are systems unto themselves, and may have internal
|
|||
|
pipelines and queues that need to be traversed before the information is
|
|||
|
even put on the USB serial bus to be transmitted to the host.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Analog sensors have an inherent tension between random noise and sensor
|
|||
|
bandwidth, and some combination of analog and digital filtering is usually
|
|||
|
done on a signal before returning it. Sometimes this filtering is
|
|||
|
excessive, which can contribute significant latency and remove subtle
|
|||
|
motions completely.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Communication bandwidth delay on older serial ports or wireless links can
|
|||
|
be significant in some cases. If the sensor messages occupy the full
|
|||
|
bandwidth of a communication channel, latency equal to the repeat time of
|
|||
|
the sensor is added simply for transferring the message. Video data
|
|||
|
streams can stress even modern wired links, which may encourage the use of
|
|||
|
data compression, which usually adds another full frame of latency if not
|
|||
|
explicitly implemented in a pipelined manner.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Filtering and communication are constant delays, but the discretely
|
|||
|
packetized nature of most sensor updates introduces a variable latency, or
|
|||
|
“jitter” as the sensor data is used for a video frame rate that differs
|
|||
|
from the sensor frame rate. This latency ranges from close to zero if the
|
|||
|
sensor packet arrived just before it was queried, up to the repeat time
|
|||
|
for sensor messages. Most USB HID devices update at 125 samples per
|
|||
|
second, giving a jitter of up to 8 milliseconds, but it is possible to
|
|||
|
receive 1000 updates a second from some USB hardware. The operating system
|
|||
|
may impose an additional random delay of up to a couple milliseconds
|
|||
|
between the arrival of a message and a user mode application getting the
|
|||
|
chance to process it, even on an unloaded system.
|
|||
|
</p>
|
|||
|
<h4 id="displays">Displays</h4>
|
|||
|
<p>
|
|||
|
On old CRT displays, the voltage coming out of the video card directly
|
|||
|
modulated the voltage of the electron gun, which caused the screen
|
|||
|
phosphors to begin emitting photons a few microseconds after a pixel was
|
|||
|
read from the frame buffer memory.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Early LCDs were notorious for “ghosting” during scrolling or animation,
|
|||
|
still showing traces of old images many tens of milliseconds after the
|
|||
|
image was changed, but significant progress has been made in the last two
|
|||
|
decades. The transition times for LCD pixels vary based on the start and
|
|||
|
end values being transitioned between, but a good panel today will have a
|
|||
|
switching time around ten milliseconds, and optimized displays for active
|
|||
|
3D and gaming can have switching times less than half that.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Modern displays are also expected to perform a wide variety of processing
|
|||
|
on the incoming signal before they change the actual display elements. A
|
|||
|
typical Full HD display today will accept 720p or interlaced composite
|
|||
|
signals and convert them to the 1920×1080 physical pixels. 24 fps movie
|
|||
|
footage will be converted to 60 fps refresh rates. Stereoscopic input may
|
|||
|
be converted from side-by-side, top-down, or other formats to frame
|
|||
|
sequential for active displays, or interlaced for passive displays.
|
|||
|
Content protection may be applied. Many consumer oriented displays have
|
|||
|
started applying motion interpolation and other sophisticated algorithms
|
|||
|
that require multiple frames of buffering.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Some of these processing tasks could be handled by only buffering a single
|
|||
|
scan line, but some of them fundamentally need one or more full frames of
|
|||
|
buffering, and display vendors have tended to implement the general case
|
|||
|
without optimizing for the cases that could be done with low or no delay.
|
|||
|
Some consumer displays wind up buffering three or more frames internally,
|
|||
|
resulting in 50 milliseconds of latency even when the input data could
|
|||
|
have been fed directly into the display matrix.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Some less common display technologies have speed advantages over LCD
|
|||
|
panels; OLED pixels can have switching times well under a millisecond, and
|
|||
|
laser displays are as instantaneous as CRTs.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
A subtle latency point is that most displays present an image
|
|||
|
incrementally as it is scanned out from the computer, which has the effect
|
|||
|
that the bottom of the screen changes 16 milliseconds later than the top
|
|||
|
of the screen on a 60 fps display. This is rarely a problem on a static
|
|||
|
display, but on a head mounted display it can cause the world to appear to
|
|||
|
shear left and right, or “waggle” as the head is rotated, because the
|
|||
|
source image was generated for an instant in time, but different parts are
|
|||
|
presented at different times. This effect is usually masked by switching
|
|||
|
times on LCD HMDs, but it is obvious with fast OLED HMDs.
|
|||
|
</p>
|
|||
|
<h4 id="host-processing">Host processing</h4>
|
|||
|
<p>The classic processing model for a game or VR application is:</p>
|
|||
|
<pre><code>Read user input -> run simulation -> issue rendering commands -> graphics drawing -> wait for vsync -> scanout
|
|||
|
|
|||
|
I = Input sampling and dependent calculation
|
|||
|
S = simulation / game execution
|
|||
|
R = rendering engine
|
|||
|
G = GPU drawing time
|
|||
|
V = video scanout time
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
All latencies are based on a frame time of roughly 16 milliseconds, a
|
|||
|
progressively scanned display, and zero sensor and pixel latency.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
If the performance demands of the application are well below what the
|
|||
|
system can provide, a straightforward implementation with no parallel
|
|||
|
overlap will usually provide fairly good latency values. However, if
|
|||
|
running synchronized to the video refresh, the minimum latency will still
|
|||
|
be 16 ms even if the system is infinitely fast. This rate feels good for
|
|||
|
most eye-hand tasks, but it is still a perceptible lag that can be felt in
|
|||
|
a head mounted display, or in the responsiveness of a mouse cursor.
|
|||
|
</p>
|
|||
|
<pre><code>Ample performance, vsync:
|
|||
|
ISRG------------|VVVVVVVVVVVVVVVV|
|
|||
|
.................. latency 16 – 32 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
Running without vsync on a very fast system will deliver better latency,
|
|||
|
but only over a fraction of the screen, and with visible tear lines. The
|
|||
|
impact of the tear lines are related to the disparity between the two
|
|||
|
frames that are being torn between, and the amount of time that the tear
|
|||
|
lines are visible. Tear lines look worse on a continuously illuminated LCD
|
|||
|
than on a CRT or laser projector, and worse on a 60 fps display than a 120
|
|||
|
fps display. Somewhat counteracting that, slow switching LCD panels blur
|
|||
|
the impact of the tear line relative to the faster displays.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
If enough frames were rendered such that each scan line had a unique
|
|||
|
image, the effect would be of a “rolling shutter”, rather than visible
|
|||
|
tear lines, and the image would feel continuous. Unfortunately, even
|
|||
|
rendering 1000 frames a second, giving approximately 15 bands on screen
|
|||
|
separated by tear lines, is still quite objectionable on fast switching
|
|||
|
displays, and few scenes are capable of being rendered at that rate, let
|
|||
|
alone 60x higher for a true rolling shutter on a 1080P display.
|
|||
|
</p>
|
|||
|
<pre><code>Ample performance, unsynchronized:
|
|||
|
ISRG
|
|||
|
VVVVV
|
|||
|
..... latency 5 – 8 milliseconds at ~200 frames per second
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
In most cases, performance is a constant point of concern, and a parallel
|
|||
|
pipelined architecture is adopted to allow multiple processors to work in
|
|||
|
parallel instead of sequentially. Large command buffers on GPUs can buffer
|
|||
|
an entire frame of drawing commands, which allows them to overlap the work
|
|||
|
on the CPU, which generally gives a significant frame rate boost at the
|
|||
|
expense of added latency.
|
|||
|
</p>
|
|||
|
<pre><code>CPU:ISSSSSRRRRRR----|
|
|||
|
GPU: |GGGGGGGGGGG----|
|
|||
|
VID: | |VVVVVVVVVVVVVVVV|
|
|||
|
.................................. latency 32 – 48 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
When the CPU load for the simulation and rendering no longer fit in a
|
|||
|
single frame, multiple CPU cores can be used in parallel to produce more
|
|||
|
frames. It is possible to reduce frame execution time without increasing
|
|||
|
latency in some cases, but the natural split of simulation and rendering
|
|||
|
has often been used to allow effective pipeline parallel operation. Work
|
|||
|
queue approaches buffered for maximum overlap can cause an additional
|
|||
|
frame of latency if they are on the critical user responsiveness path.
|
|||
|
</p>
|
|||
|
<pre><code>CPU1:ISSSSSSSS-------|
|
|||
|
CPU2: |RRRRRRRRR-------|
|
|||
|
GPU : | |GGGGGGGGGG------|
|
|||
|
VID : | | |VVVVVVVVVVVVVVVV|
|
|||
|
.................................................... latency 48 – 64 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
Even if an application is running at a perfectly smooth 60 fps, it can
|
|||
|
still have host latencies of over 50 milliseconds, and an application
|
|||
|
targeting 30 fps could have twice that. Sensor and display latencies can
|
|||
|
add significant additional amounts on top of that, so the goal of 20
|
|||
|
milliseconds motion-to-photons latency is challenging to achieve.
|
|||
|
</p>
|
|||
|
<h4 id="latency-reduction-strategies">Latency Reduction Strategies</h4>
|
|||
|
<h4 id="prevent-gpu-buffering">Prevent GPU buffering</h4>
|
|||
|
<p>
|
|||
|
The drive to win frame rate benchmark wars has led driver writers to
|
|||
|
aggressively buffer drawing commands, and there have even been cases where
|
|||
|
drivers ignored explicit calls to glFinish() in the name of improved
|
|||
|
“performance”. Today’s fence primitives do appear to be reliably observed
|
|||
|
for drawing primitives, but the semantics of buffer swaps are still
|
|||
|
worryingly imprecise. A recommended sequence of commands to synchronize
|
|||
|
with the vertical retrace and idle the GPU is:
|
|||
|
</p>
|
|||
|
<pre><code>SwapBuffers();
|
|||
|
DrawTinyPrimitive();
|
|||
|
InsertGPUFence();
|
|||
|
BlockUntilFenceIsReached();
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
While this should always prevent excessive command buffering on any
|
|||
|
conformant driver, it could conceivably fail to provide an accurate
|
|||
|
vertical sync timing point if the driver was transparently implementing
|
|||
|
triple buffering.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
To minimize the performance impact of synchronizing with the GPU, it is
|
|||
|
important to have sufficient work ready to send to the GPU immediately
|
|||
|
after the synchronization is performed. The details of exactly when the
|
|||
|
GPU can begin executing commands are platform specific, but execution can
|
|||
|
be explicitly kicked off with glFlush() or equivalent calls. If the code
|
|||
|
issuing drawing commands does not proceed fast enough, the GPU may
|
|||
|
complete all the work and go idle with a “pipeline bubble”. Because the
|
|||
|
CPU time to issue a drawing command may have little relation to the GPU
|
|||
|
time required to draw it, these pipeline bubbles may cause the GPU to take
|
|||
|
noticeably longer to draw the frame than if it were completely buffered.
|
|||
|
Ordering the drawing so that larger and slower operations happen first
|
|||
|
will provide a cushion, as will pushing as much preparatory work as
|
|||
|
possible before the synchronization point.
|
|||
|
</p>
|
|||
|
<pre><code>Run GPU with minimal buffering:
|
|||
|
CPU1:ISSSSSSSS-------|
|
|||
|
CPU2: |RRRRRRRRR-------|
|
|||
|
GPU : |-GGGGGGGGGG-----|
|
|||
|
VID : | |VVVVVVVVVVVVVVVV|
|
|||
|
................................... latency 32 – 48 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
Tile based renderers, as are found in most mobile devices, inherently
|
|||
|
require a full scene of command buffering before they can generate their
|
|||
|
first tile of pixels, so synchronizing before issuing any commands will
|
|||
|
destroy far more overlap. In a modern rendering engine there may be
|
|||
|
multiple scene renders for each frame to handle shadows, reflections, and
|
|||
|
other effects, but increased latency is still a fundamental drawback of
|
|||
|
the technology.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
High end, multiple GPU systems today are usually configured for AFR, or
|
|||
|
Alternate Frame Rendering, where each GPU is allowed to take twice as long
|
|||
|
to render a single frame, but the overall frame rate is maintained because
|
|||
|
there are two GPUs producing frames
|
|||
|
</p>
|
|||
|
<pre><code>Alternate Frame Rendering dual GPU:
|
|||
|
CPU1:IOSSSSSSS-------|IOSSSSSSS-------|
|
|||
|
CPU2: |RRRRRRRRR-------|RRRRRRRRR-------|
|
|||
|
GPU1: | GGGGGGGGGGGGGGGGGGGGGGGG--------|
|
|||
|
GPU2: | | GGGGGGGGGGGGGGGGGGGGGGG---------|
|
|||
|
VID : | | |VVVVVVVVVVVVVVVV|
|
|||
|
.................................................... latency 48 – 64 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
Similarly to the case with CPU workloads, it is possible to have two or
|
|||
|
more GPUs cooperate on a single frame in a way that delivers more work in
|
|||
|
a constant amount of time, but it increases complexity and generally
|
|||
|
delivers a lower total speedup.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
An attractive direction for stereoscopic rendering is to have each GPU on
|
|||
|
a dual GPU system render one eye, which would deliver maximum performance
|
|||
|
and minimum latency, at the expense of requiring the application to
|
|||
|
maintain buffers across two independent rendering contexts.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
The downside to preventing GPU buffering is that throughput performance
|
|||
|
may drop, resulting in more dropped frames under heavily loaded
|
|||
|
conditions.
|
|||
|
</p>
|
|||
|
<h4 id="late-frame-scheduling">Late frame scheduling</h4>
|
|||
|
<p>
|
|||
|
Much of the work in the simulation task does not depend directly on the
|
|||
|
user input, or would be insensitive to a frame of latency in it. If the
|
|||
|
user processing is done last, and the input is sampled just before it is
|
|||
|
needed, rather than stored off at the beginning of the frame, the total
|
|||
|
latency can be reduced.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
It is very difficult to predict the time required for the general
|
|||
|
simulation work on the entire world, but the work just for the player’s
|
|||
|
view response to the sensor input can be made essentially deterministic.
|
|||
|
If this is split off from the main simulation task and delayed until
|
|||
|
shortly before the end of the frame, it can remove nearly a full frame of
|
|||
|
latency.
|
|||
|
</p>
|
|||
|
<pre><code>Late frame scheduling:
|
|||
|
CPU1:SSSSSSSSS------I|
|
|||
|
CPU2: |RRRRRRRRR-------|
|
|||
|
GPU : |-GGGGGGGGGG-----|
|
|||
|
VID : | |VVVVVVVVVVVVVVVV|
|
|||
|
.................... latency 18 – 34 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
Adjusting the view is the most latency sensitive task; actions resulting
|
|||
|
from other user commands, like animating a weapon or interacting with
|
|||
|
other objects in the world, are generally insensitive to an additional
|
|||
|
frame of latency, and can be handled in the general simulation task the
|
|||
|
following frame.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
The drawback to late frame scheduling is that it introduces a tight
|
|||
|
scheduling requirement that usually requires busy waiting to meet, wasting
|
|||
|
power. If your frame rate is determined by the video retrace rather than
|
|||
|
an arbitrary time slice, assistance from the graphics driver in accurately
|
|||
|
determining the current scanout position is helpful.
|
|||
|
</p>
|
|||
|
<h4 id="view-bypass">View bypass</h4>
|
|||
|
<p>
|
|||
|
An alternate way of accomplishing a similar, or slightly greater latency
|
|||
|
reduction Is to allow the rendering code to modify the parameters
|
|||
|
delivered to it by the game code, based on a newer sampling of user input.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
At the simplest level, the user input can be used to calculate a delta
|
|||
|
from the previous sampling to the current one, which can be used to modify
|
|||
|
the view matrix that the game submitted to the rendering code.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Delta processing in this way is minimally intrusive, but there will often
|
|||
|
be situations where the user input should not affect the rendering, such
|
|||
|
as cinematic cut scenes or when the player has died. It can be argued that
|
|||
|
a game designed from scratch for virtual reality should avoid those
|
|||
|
situations, because a non-responsive view in a HMD is disorienting and
|
|||
|
unpleasant, but conventional game design has many such cases.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
A binary flag could be provided to disable the bypass calculation, but it
|
|||
|
is useful to generalize such that the game provides an object or function
|
|||
|
with embedded state that produces rendering parameters from sensor input
|
|||
|
data instead of having the game provide the view parameters themselves. In
|
|||
|
addition to handling the trivial case of ignoring sensor input, the
|
|||
|
generator function can incorporate additional information such as a
|
|||
|
head/neck positioning model that modified position based on orientation,
|
|||
|
or lists of other models to be positioned relative to the updated view.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
If the game and rendering code are running in parallel, it is important
|
|||
|
that the parameter generation function does not reference any game state
|
|||
|
to avoid race conditions.
|
|||
|
</p>
|
|||
|
<pre><code>View bypass:
|
|||
|
CPU1:ISSSSSSSSS------|
|
|||
|
CPU2: |IRRRRRRRRR------|
|
|||
|
GPU : |--GGGGGGGGGG----|
|
|||
|
VID : | |VVVVVVVVVVVVVVVV|
|
|||
|
.................. latency 16 – 32 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
The input is only sampled once per frame, but it is simultaneously used by
|
|||
|
both the simulation task and the rendering task. Some input processing
|
|||
|
work is now duplicated by the simulation task and the render task, but it
|
|||
|
is generally minimal.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
The latency for parameters produced by the generator function is now
|
|||
|
reduced, but other interactions with the world, like muzzle flashes and
|
|||
|
physics responses, remain at the same latency as the standard model.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
A modified form of view bypass could allow tile based GPUs to achieve
|
|||
|
similar view latencies to non-tiled GPUs, or allow non-tiled GPUs to
|
|||
|
achieve 100% utilization without pipeline bubbles by the following steps:
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Inhibit the execution of GPU commands, forcing them to be buffered. OpenGL
|
|||
|
has only the deprecated display list functionality to approximate this,
|
|||
|
but a control extension could be formulated.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
All calculations that depend on the view matrix must reference it
|
|||
|
independently from a buffer object, rather than from inline parameters or
|
|||
|
as a composite model-view-projection (MVP) matrix.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
After all commands have been issued and the next frame has started, sample
|
|||
|
the user input, run it through the parameter generator, and put the
|
|||
|
resulting view matrix into the buffer object for referencing by the draw
|
|||
|
commands.
|
|||
|
</p>
|
|||
|
<p>Kick off the draw command execution.</p>
|
|||
|
<pre><code>Tiler optimized view bypass:
|
|||
|
CPU1:ISSSSSSSSS------|
|
|||
|
CPU2: |IRRRRRRRRRR-----|I
|
|||
|
GPU : | |-GGGGGGGGGG-----|
|
|||
|
VID : | | |VVVVVVVVVVVVVVVV|
|
|||
|
.................. latency 16 – 32 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
Any view frustum culling that was performed to avoid drawing some models
|
|||
|
may be invalid if the new view matrix has changed substantially enough
|
|||
|
from what was used during the rendering task. This can be mitigated at
|
|||
|
some performance cost by using a larger frustum field of view for culling,
|
|||
|
and hardware clip planes based on the culling frustum limits can be used
|
|||
|
to guarantee a clean edge if necessary. Occlusion errors from culling,
|
|||
|
where a bright object is seen that should have been occluded by an object
|
|||
|
that was incorrectly culled, are very distracting, but a temporary clean
|
|||
|
encroaching of black at a screen edge during rapid rotation is almost
|
|||
|
unnoticeable.
|
|||
|
</p>
|
|||
|
<h4 id="time-warping">Time warping</h4>
|
|||
|
<p>
|
|||
|
If you had perfect knowledge of how long the rendering of a frame would
|
|||
|
take, some additional amount of latency could be saved by late frame
|
|||
|
scheduling the entire rendering task, but this is not practical due to the
|
|||
|
wide variability in frame rendering times.
|
|||
|
</p>
|
|||
|
<pre><code>Late frame input sampled view bypass:
|
|||
|
CPU1:ISSSSSSSSS------|
|
|||
|
CPU2: |----IRRRRRRRRR--|
|
|||
|
GPU : |------GGGGGGGGGG|
|
|||
|
VID : | |VVVVVVVVVVVVVVVV|
|
|||
|
.............. latency 12 – 28 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
However, a post processing task on the rendered image can be counted on to
|
|||
|
complete in a fairly predictable amount of time, and can be late scheduled
|
|||
|
more easily. Any pixel on the screen, along with the associated depth
|
|||
|
buffer value, can be converted back to a world space position, which can
|
|||
|
be re-transformed to a different screen space pixel location for a
|
|||
|
modified set of view parameters.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
After drawing a frame with the best information at your disposal, possibly
|
|||
|
with bypassed view parameters, instead of displaying it directly, fetch
|
|||
|
the latest user input, generate updated view parameters, and calculate a
|
|||
|
transformation that warps the rendered image into a position that
|
|||
|
approximates where it would be with the updated parameters. Using that
|
|||
|
transform, warp the rendered image into an updated form on screen that
|
|||
|
reflects the new input. If there are two dimensional overlays present on
|
|||
|
the screen that need to remain fixed, they must be drawn or composited in
|
|||
|
after the warp operation, to prevent them from incorrectly moving as the
|
|||
|
view parameters change.
|
|||
|
</p>
|
|||
|
<pre><code>Late frame scheduled time warp:
|
|||
|
CPU1:ISSSSSSSSS------|
|
|||
|
CPU2: |RRRRRRRRRR----IR|
|
|||
|
GPU : |-GGGGGGGGGG----G|
|
|||
|
VID : | |VVVVVVVVVVVVVVVV|
|
|||
|
.... latency 2 – 18 milliseconds
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
If the difference between the view parameters at the time of the scene
|
|||
|
rendering and the time of the final warp is only a change in direction,
|
|||
|
the warped image can be almost exactly correct within the limits of the
|
|||
|
image filtering. Effects that are calculated relative to the screen, like
|
|||
|
depth based fog (versus distance based fog) and billboard sprites will be
|
|||
|
slightly different, but not in a manner that is objectionable.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
If the warp involves translation as well as direction changes, geometric
|
|||
|
silhouette edges begin to introduce artifacts where internal parallax
|
|||
|
would have revealed surfaces not visible in the original rendering. A
|
|||
|
scene with no silhouette edges, like the inside of a box, can be warped
|
|||
|
significant amounts and display only changes in texture density, but
|
|||
|
translation warping realistic scenes will result in smears or gaps along
|
|||
|
edges. In many cases these are difficult to notice, and they always
|
|||
|
disappear when motion stops, but first person view hands and weapons are a
|
|||
|
prominent case. This can be mitigated by limiting the amount of
|
|||
|
translation warp, compressing or making constant the depth range of the
|
|||
|
scene being warped to limit the dynamic separation, or rendering the
|
|||
|
disconnected near field objects as a separate plane, to be composited in
|
|||
|
after the warp.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
If an image is being warped to a destination with the same field of view,
|
|||
|
most warps will leave some corners or edges of the new image undefined,
|
|||
|
because none of the source pixels are warped to their locations. This can
|
|||
|
be mitigated by rendering a larger field of view than the destination
|
|||
|
requires; but simply leaving unrendered pixels black is surprisingly
|
|||
|
unobtrusive, especially in a wide field of view HMD.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
A forward warp, where source pixels are deposited in their new positions,
|
|||
|
offers the best accuracy for arbitrary transformations. At the limit, the
|
|||
|
frame buffer and depth buffer could be treated as a height field, but
|
|||
|
millions of half pixel sized triangles would have a severe performance
|
|||
|
cost. Using a grid of triangles at some fraction of the depth buffer
|
|||
|
resolution can bring the cost down to a very low level, and the trivial
|
|||
|
case of treating the rendered image as a single quad avoids all silhouette
|
|||
|
artifacts at the expense of incorrect pixel positions under translation.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Reverse warping, where the pixel in the source rendering is estimated
|
|||
|
based on the position in the warped image, can be more convenient because
|
|||
|
it is implemented completely in a fragment shader. It can produce
|
|||
|
identical results for simple direction changes, but additional artifacts
|
|||
|
near geometric boundaries are introduced if per-pixel depth information is
|
|||
|
considered, unless considerable effort is expended to search a
|
|||
|
neighborhood for the best source pixel.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
If desired, it is straightforward to incorporate motion blur in a reverse
|
|||
|
mapping by taking several samples along the line from the pixel being
|
|||
|
warped to the transformed position in the source image.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Reverse mapping also allows the possibility of modifying the warp through
|
|||
|
the video scanout. The view parameters can be predicted ahead in time to
|
|||
|
when the scanout will read the bottom row of pixels, which can be used to
|
|||
|
generate a second warp matrix. The warp to be applied can be interpolated
|
|||
|
between the two of them based on the pixel row being processed. This can
|
|||
|
correct for the “waggle” effect on a progressively scanned head mounted
|
|||
|
display, where the 16 millisecond difference in time between the display
|
|||
|
showing the top line and bottom line results in a perceived shearing of
|
|||
|
the world under rapid rotation on fast switching displays.
|
|||
|
</p>
|
|||
|
<h4 id="continuously-updated-time-warping">
|
|||
|
Continuously updated time warping
|
|||
|
</h4>
|
|||
|
<p>
|
|||
|
If the necessary feedback and scheduling mechanisms are available, instead
|
|||
|
of predicting what the warp transformation should be at the bottom of the
|
|||
|
frame and warping the entire screen at once, the warp to screen can be
|
|||
|
done incrementally while continuously updating the warp matrix as new
|
|||
|
input arrives.
|
|||
|
</p>
|
|||
|
<pre><code>Continuous time warp:
|
|||
|
CPU1:ISSSSSSSSS------|
|
|||
|
CPU2: |RRRRRRRRRRR-----|
|
|||
|
GPU : |-GGGGGGGGGGGG---|
|
|||
|
WARP: | W| W W W W W W W W|
|
|||
|
VID : | |VVVVVVVVVVVVVVVV|
|
|||
|
... latency 2 – 3 milliseconds for 500hz sensor updates
|
|||
|
</code></pre>
|
|||
|
<p>
|
|||
|
The ideal interface for doing this would be some form of “scanout shader”
|
|||
|
that would be called “just in time” for the video display. Several video
|
|||
|
game systems like the Atari 2600, Jaguar, and Nintendo DS have had buffers
|
|||
|
ranging from half a scan line to several scan lines that were filled up in
|
|||
|
this manner.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Without new hardware support, it is still possible to incrementally
|
|||
|
perform the warping directly to the front buffer being scanned for video,
|
|||
|
and not perform a swap buffers operation at all.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
A CPU core could be dedicated to the task of warping scan lines at roughly
|
|||
|
the speed they are consumed by the video output, updating the time warp
|
|||
|
matrix each scan line to blend in the most recently arrived sensor
|
|||
|
information.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
GPUs can perform the time warping operation much more efficiently than a
|
|||
|
conventional CPU can, but the GPU will be busy drawing the next frame
|
|||
|
during video scanout, and GPU drawing operations cannot currently be
|
|||
|
scheduled with high precision due to the difficulty of task switching the
|
|||
|
deep pipelines and extensive context state. However, modern GPUs are
|
|||
|
beginning to allow compute tasks to run in parallel with graphics
|
|||
|
operations, which may allow a fraction of a GPU to be dedicated to
|
|||
|
performing the warp operations as a shared parameter buffer is updated by
|
|||
|
the CPU.
|
|||
|
</p>
|
|||
|
<h4 id="discussion">Discussion</h4>
|
|||
|
<p>
|
|||
|
View bypass and time warping are complementary techniques that can be
|
|||
|
applied independently or together. Time warping can warp from a source
|
|||
|
image at an arbitrary view time / location to any other one, but artifacts
|
|||
|
from internal parallax and screen edge clamping are reduced by using the
|
|||
|
most recent source image possible, which view bypass rendering helps
|
|||
|
provide.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Actions that require simulation state changes, like flipping a switch or
|
|||
|
firing a weapon, still need to go through the full pipeline for 32 – 48
|
|||
|
milliseconds of latency based on what scan line the result winds up
|
|||
|
displaying on the screen, and translational information may not be
|
|||
|
completely faithfully represented below the 16 – 32 milliseconds of the
|
|||
|
view bypass rendering, but the critical head orientation feedback can be
|
|||
|
provided in 2 – 18 milliseconds on a 60 hz display. In conjunction with
|
|||
|
low latency sensors and displays, this will generally be perceived as
|
|||
|
immediate. Continuous time warping opens up the possibility of latencies
|
|||
|
below 3 milliseconds, which may cross largely unexplored thresholds in
|
|||
|
human / computer interactivity.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Conventional computer interfaces are generally not as latency demanding as
|
|||
|
virtual reality, but sensitive users can tell the difference in mouse
|
|||
|
response down to the same 20 milliseconds or so, making it worthwhile to
|
|||
|
apply these techniques even in applications without a VR focus.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
A particularly interesting application is in “cloud gaming”, where a
|
|||
|
simple client appliance or application forwards control information to a
|
|||
|
remote server, which streams back real time video of the game. This offers
|
|||
|
significant convenience benefits for users, but the inherent network and
|
|||
|
compression latencies makes it a lower quality experience for action
|
|||
|
oriented titles. View bypass and time warping can both be performed on the
|
|||
|
server, regaining a substantial fraction of the latency imposed by the
|
|||
|
network. If the cloud gaming client was made more sophisticated, time
|
|||
|
warping could be performed locally, which could theoretically reduce the
|
|||
|
latency to the same levels as local applications, but it would probably be
|
|||
|
prudent to restrict the total amount of time warping to perhaps 30 or 40
|
|||
|
milliseconds to limit the distance from the source images.
|
|||
|
</p>
|
|||
|
<h4 id="acknowledgements">Acknowledgements</h4>
|
|||
|
<p>Zenimax for allowing me to publish this openly.</p>
|
|||
|
<p>Hillcrest Labs for inertial sensors and experimental firmware.</p>
|
|||
|
<p>Emagin for access to OLED displays.</p>
|
|||
|
<p>Oculus for a prototype Rift HMD.</p>
|
|||
|
<p>
|
|||
|
Nvidia for an experimental driver with access to the current scan line
|
|||
|
number.
|
|||
|
</p>
|
|||
|
<div class="np">
|
|||
|
<a href="https://danluu.com/about/">← About danluu.com</a>
|
|||
|
<a href="https://danluu.com/karajack/"
|
|||
|
>Kara Swisher interview of Jack Dorsey →</a
|
|||
|
>
|
|||
|
</div>
|
|||
|
<div class="np">
|
|||
|
<a href="https://danluu.com/">Archive</a>
|
|||
|
<a href="https://twitter.com/danluu">Twitter</a>
|
|||
|
</div>
|
|||
|
<div
|
|||
|
class="webext-omnivore-toast"
|
|||
|
style="
|
|||
|
color: rgb(61, 61, 61) !important;
|
|||
|
font: 700 13px Inter, sans-serif !important;
|
|||
|
font-feature-settings: initial !important;
|
|||
|
font-kerning: initial !important;
|
|||
|
font-optical-sizing: initial !important;
|
|||
|
font-palette: initial !important;
|
|||
|
font-synthesis: initial !important;
|
|||
|
font-variation-settings: initial !important;
|
|||
|
forced-color-adjust: initial !important;
|
|||
|
text-orientation: initial !important;
|
|||
|
text-rendering: initial !important;
|
|||
|
-webkit-font-smoothing: initial !important;
|
|||
|
-webkit-locale: initial !important;
|
|||
|
-webkit-text-orientation: initial !important;
|
|||
|
-webkit-writing-mode: initial !important;
|
|||
|
writing-mode: initial !important;
|
|||
|
zoom: initial !important;
|
|||
|
accent-color: initial !important;
|
|||
|
align-content: initial !important;
|
|||
|
align-items: center !important;
|
|||
|
place-self: initial !important;
|
|||
|
alignment-baseline: initial !important;
|
|||
|
animation: initial !important;
|
|||
|
app-region: initial !important;
|
|||
|
appearance: initial !important;
|
|||
|
aspect-ratio: initial !important;
|
|||
|
backdrop-filter: initial !important;
|
|||
|
backface-visibility: initial !important;
|
|||
|
background: rgb(255, 255, 255) !important;
|
|||
|
background-blend-mode: initial !important;
|
|||
|
baseline-shift: initial !important;
|
|||
|
block-size: initial !important;
|
|||
|
border-block: initial !important;
|
|||
|
border: initial !important;
|
|||
|
border-radius: 10px !important;
|
|||
|
border-collapse: initial !important;
|
|||
|
border-end-end-radius: initial !important;
|
|||
|
border-end-start-radius: initial !important;
|
|||
|
border-inline: initial !important;
|
|||
|
border-start-end-radius: initial !important;
|
|||
|
border-start-start-radius: initial !important;
|
|||
|
bottom: initial !important;
|
|||
|
box-shadow: rgba(57, 59, 67, 0.25) 0px 1px 89px !important;
|
|||
|
box-sizing: initial !important;
|
|||
|
break-after: initial !important;
|
|||
|
break-before: initial !important;
|
|||
|
break-inside: initial !important;
|
|||
|
buffered-rendering: initial !important;
|
|||
|
caption-side: initial !important;
|
|||
|
caret-color: initial !important;
|
|||
|
clear: initial !important;
|
|||
|
clip: initial !important;
|
|||
|
clip-path: initial !important;
|
|||
|
clip-rule: initial !important;
|
|||
|
color-interpolation: initial !important;
|
|||
|
color-interpolation-filters: initial !important;
|
|||
|
color-rendering: initial !important;
|
|||
|
color-scheme: initial !important;
|
|||
|
columns: initial !important;
|
|||
|
column-fill: initial !important;
|
|||
|
gap: initial !important;
|
|||
|
column-rule: initial !important;
|
|||
|
column-span: initial !important;
|
|||
|
contain: initial !important;
|
|||
|
contain-intrinsic-block-size: initial !important;
|
|||
|
contain-intrinsic-size: initial !important;
|
|||
|
contain-intrinsic-inline-size: initial !important;
|
|||
|
content: initial !important;
|
|||
|
content-visibility: initial !important;
|
|||
|
counter-increment: initial !important;
|
|||
|
counter-reset: initial !important;
|
|||
|
counter-set: initial !important;
|
|||
|
cursor: initial !important;
|
|||
|
cx: initial !important;
|
|||
|
cy: initial !important;
|
|||
|
d: initial !important;
|
|||
|
display: flex !important;
|
|||
|
dominant-baseline: initial !important;
|
|||
|
empty-cells: initial !important;
|
|||
|
fill: currentcolor !important;
|
|||
|
fill-opacity: initial !important;
|
|||
|
fill-rule: initial !important;
|
|||
|
filter: initial !important;
|
|||
|
flex: initial !important;
|
|||
|
flex-flow: initial !important;
|
|||
|
float: initial !important;
|
|||
|
flood-color: initial !important;
|
|||
|
flood-opacity: initial !important;
|
|||
|
grid: initial !important;
|
|||
|
grid-area: initial !important;
|
|||
|
height: 80px !important;
|
|||
|
hyphens: initial !important;
|
|||
|
image-orientation: initial !important;
|
|||
|
image-rendering: initial !important;
|
|||
|
inline-size: initial !important;
|
|||
|
inset-block: initial !important;
|
|||
|
inset-inline: initial !important;
|
|||
|
isolation: initial !important;
|
|||
|
justify-content: center !important;
|
|||
|
justify-items: initial !important;
|
|||
|
left: initial !important;
|
|||
|
letter-spacing: initial !important;
|
|||
|
lighting-color: initial !important;
|
|||
|
line-break: initial !important;
|
|||
|
list-style: initial !important;
|
|||
|
margin-block: initial !important;
|
|||
|
margin: initial !important;
|
|||
|
margin-inline: initial !important;
|
|||
|
marker: initial !important;
|
|||
|
mask: initial !important;
|
|||
|
mask-type: initial !important;
|
|||
|
max-block-size: initial !important;
|
|||
|
max-height: initial !important;
|
|||
|
max-inline-size: initial !important;
|
|||
|
max-width: initial !important;
|
|||
|
min-block-size: initial !important;
|
|||
|
min-height: initial !important;
|
|||
|
min-inline-size: initial !important;
|
|||
|
min-width: initial !important;
|
|||
|
mix-blend-mode: initial !important;
|
|||
|
object-fit: initial !important;
|
|||
|
object-position: initial !important;
|
|||
|
offset: initial !important;
|
|||
|
opacity: initial !important;
|
|||
|
order: initial !important;
|
|||
|
origin-trial-test-property: initial !important;
|
|||
|
orphans: initial !important;
|
|||
|
outline: initial !important;
|
|||
|
outline-offset: initial !important;
|
|||
|
overflow-anchor: initial !important;
|
|||
|
overflow-clip-margin: initial !important;
|
|||
|
overflow-wrap: initial !important;
|
|||
|
overflow: hidden !important;
|
|||
|
overscroll-behavior-block: initial !important;
|
|||
|
overscroll-behavior-inline: initial !important;
|
|||
|
overscroll-behavior: initial !important;
|
|||
|
padding-block: initial !important;
|
|||
|
padding: initial !important;
|
|||
|
padding-inline: initial !important;
|
|||
|
page: initial !important;
|
|||
|
page-orientation: initial !important;
|
|||
|
paint-order: initial !important;
|
|||
|
perspective: initial !important;
|
|||
|
perspective-origin: initial !important;
|
|||
|
pointer-events: initial !important;
|
|||
|
position: fixed !important;
|
|||
|
quotes: initial !important;
|
|||
|
r: initial !important;
|
|||
|
resize: initial !important;
|
|||
|
right: 45px !important;
|
|||
|
ruby-position: initial !important;
|
|||
|
rx: initial !important;
|
|||
|
ry: initial !important;
|
|||
|
scroll-behavior: initial !important;
|
|||
|
scroll-margin-block: initial !important;
|
|||
|
scroll-margin: initial !important;
|
|||
|
scroll-margin-inline: initial !important;
|
|||
|
scroll-padding-block: initial !important;
|
|||
|
scroll-padding: initial !important;
|
|||
|
scroll-padding-inline: initial !important;
|
|||
|
scroll-snap-align: initial !important;
|
|||
|
scroll-snap-stop: initial !important;
|
|||
|
scroll-snap-type: initial !important;
|
|||
|
scrollbar-gutter: initial !important;
|
|||
|
shape-image-threshold: initial !important;
|
|||
|
shape-margin: initial !important;
|
|||
|
shape-outside: initial !important;
|
|||
|
shape-rendering: initial !important;
|
|||
|
size: initial !important;
|
|||
|
speak: initial !important;
|
|||
|
stop-color: initial !important;
|
|||
|
stop-opacity: initial !important;
|
|||
|
stroke: initial !important;
|
|||
|
stroke-dasharray: initial !important;
|
|||
|
stroke-dashoffset: initial !important;
|
|||
|
stroke-linecap: initial !important;
|
|||
|
stroke-linejoin: initial !important;
|
|||
|
stroke-miterlimit: initial !important;
|
|||
|
stroke-opacity: initial !important;
|
|||
|
stroke-width: initial !important;
|
|||
|
tab-size: initial !important;
|
|||
|
table-layout: initial !important;
|
|||
|
text-align: initial !important;
|
|||
|
text-align-last: initial !important;
|
|||
|
text-anchor: initial !important;
|
|||
|
text-combine-upright: initial !important;
|
|||
|
text-decoration: initial !important;
|
|||
|
text-decoration-skip-ink: initial !important;
|
|||
|
text-emphasis: initial !important;
|
|||
|
text-emphasis-position: initial !important;
|
|||
|
text-indent: initial !important;
|
|||
|
text-overflow: initial !important;
|
|||
|
text-shadow: initial !important;
|
|||
|
text-size-adjust: initial !important;
|
|||
|
text-transform: initial !important;
|
|||
|
text-underline-offset: initial !important;
|
|||
|
text-underline-position: initial !important;
|
|||
|
top: 20px !important;
|
|||
|
touch-action: initial !important;
|
|||
|
transform: initial !important;
|
|||
|
transform-box: initial !important;
|
|||
|
transform-origin: initial !important;
|
|||
|
transform-style: initial !important;
|
|||
|
transition: all 300ms ease 0s !important;
|
|||
|
user-select: none !important;
|
|||
|
vector-effect: initial !important;
|
|||
|
vertical-align: initial !important;
|
|||
|
visibility: initial !important;
|
|||
|
border-spacing: initial !important;
|
|||
|
-webkit-border-image: initial !important;
|
|||
|
-webkit-box-align: initial !important;
|
|||
|
-webkit-box-decoration-break: initial !important;
|
|||
|
-webkit-box-direction: initial !important;
|
|||
|
-webkit-box-flex: initial !important;
|
|||
|
-webkit-box-ordinal-group: initial !important;
|
|||
|
-webkit-box-orient: initial !important;
|
|||
|
-webkit-box-pack: initial !important;
|
|||
|
-webkit-box-reflect: initial !important;
|
|||
|
-webkit-highlight: initial !important;
|
|||
|
-webkit-hyphenate-character: initial !important;
|
|||
|
-webkit-line-break: initial !important;
|
|||
|
-webkit-line-clamp: initial !important;
|
|||
|
-webkit-mask-box-image: initial !important;
|
|||
|
-webkit-mask: initial !important;
|
|||
|
-webkit-mask-composite: initial !important;
|
|||
|
-webkit-perspective-origin-x: initial !important;
|
|||
|
-webkit-perspective-origin-y: initial !important;
|
|||
|
-webkit-print-color-adjust: initial !important;
|
|||
|
-webkit-rtl-ordering: initial !important;
|
|||
|
-webkit-ruby-position: initial !important;
|
|||
|
-webkit-tap-highlight-color: initial !important;
|
|||
|
-webkit-text-combine: initial !important;
|
|||
|
-webkit-text-decorations-in-effect: initial !important;
|
|||
|
-webkit-text-fill-color: initial !important;
|
|||
|
-webkit-text-security: initial !important;
|
|||
|
-webkit-text-stroke: initial !important;
|
|||
|
-webkit-transform-origin-x: initial !important;
|
|||
|
-webkit-transform-origin-y: initial !important;
|
|||
|
-webkit-transform-origin-z: initial !important;
|
|||
|
-webkit-user-drag: initial !important;
|
|||
|
-webkit-user-modify: initial !important;
|
|||
|
white-space: initial !important;
|
|||
|
widows: initial !important;
|
|||
|
width: 240px !important;
|
|||
|
will-change: initial !important;
|
|||
|
word-break: initial !important;
|
|||
|
word-spacing: initial !important;
|
|||
|
x: initial !important;
|
|||
|
y: initial !important;
|
|||
|
z-index: 9999999 !important;
|
|||
|
"
|
|||
|
>
|
|||
|
<svg
|
|||
|
viewBox="0 0 16 16"
|
|||
|
xmlns="http://www.w3.org/2000/svg"
|
|||
|
style="
|
|||
|
color: inherit !important;
|
|||
|
font: initial !important;
|
|||
|
font-feature-settings: initial !important;
|
|||
|
font-kerning: initial !important;
|
|||
|
font-optical-sizing: initial !important;
|
|||
|
font-palette: initial !important;
|
|||
|
font-synthesis: initial !important;
|
|||
|
font-variation-settings: initial !important;
|
|||
|
forced-color-adjust: initial !important;
|
|||
|
text-orientation: initial !important;
|
|||
|
text-rendering: initial !important;
|
|||
|
-webkit-font-smoothing: initial !important;
|
|||
|
-webkit-locale: initial !important;
|
|||
|
-webkit-text-orientation: initial !important;
|
|||
|
-webkit-writing-mode: initial !important;
|
|||
|
writing-mode: initial !important;
|
|||
|
zoom: initial !important;
|
|||
|
accent-color: initial !important;
|
|||
|
place-content: initial !important;
|
|||
|
place-items: initial !important;
|
|||
|
place-self: initial !important;
|
|||
|
alignment-baseline: initial !important;
|
|||
|
animation: initial !important;
|
|||
|
app-region: initial !important;
|
|||
|
appearance: initial !important;
|
|||
|
aspect-ratio: initial !important;
|
|||
|
backdrop-filter: initial !important;
|
|||
|
backface-visibility: initial !important;
|
|||
|
background: initial !important;
|
|||
|
background-blend-mode: initial !important;
|
|||
|
baseline-shift: initial !important;
|
|||
|
block-size: initial !important;
|
|||
|
border-block: initial !important;
|
|||
|
border: initial !important;
|
|||
|
border-radius: initial !important;
|
|||
|
border-collapse: initial !important;
|
|||
|
border-end-end-radius: initial !important;
|
|||
|
border-end-start-radius: initial !important;
|
|||
|
border-inline: initial !important;
|
|||
|
border-start-end-radius: initial !important;
|
|||
|
border-start-start-radius: initial !important;
|
|||
|
inset: initial !important;
|
|||
|
box-shadow: initial !important;
|
|||
|
box-sizing: initial !important;
|
|||
|
break-after: initial !important;
|
|||
|
break-before: initial !important;
|
|||
|
break-inside: initial !important;
|
|||
|
buffered-rendering: initial !important;
|
|||
|
caption-side: initial !important;
|
|||
|
caret-color: initial !important;
|
|||
|
clear: initial !important;
|
|||
|
clip: initial !important;
|
|||
|
clip-path: initial !important;
|
|||
|
clip-rule: initial !important;
|
|||
|
color-interpolation: initial !important;
|
|||
|
color-interpolation-filters: initial !important;
|
|||
|
color-rendering: initial !important;
|
|||
|
color-scheme: initial !important;
|
|||
|
columns: initial !important;
|
|||
|
column-fill: initial !important;
|
|||
|
gap: initial !important;
|
|||
|
column-rule: initial !important;
|
|||
|
column-span: initial !important;
|
|||
|
contain: initial !important;
|
|||
|
contain-intrinsic-block-size: initial !important;
|
|||
|
contain-intrinsic-size: initial !important;
|
|||
|
contain-intrinsic-inline-size: initial !important;
|
|||
|
content: initial !important;
|
|||
|
content-visibility: initial !important;
|
|||
|
counter-increment: initial !important;
|
|||
|
counter-reset: initial !important;
|
|||
|
counter-set: initial !important;
|
|||
|
cursor: initial !important;
|
|||
|
cx: initial !important;
|
|||
|
cy: initial !important;
|
|||
|
d: initial !important;
|
|||
|
display: initial !important;
|
|||
|
dominant-baseline: initial !important;
|
|||
|
empty-cells: initial !important;
|
|||
|
fill: inherit !important;
|
|||
|
fill-opacity: initial !important;
|
|||
|
fill-rule: initial !important;
|
|||
|
filter: initial !important;
|
|||
|
flex: initial !important;
|
|||
|
flex-flow: initial !important;
|
|||
|
float: initial !important;
|
|||
|
flood-color: initial !important;
|
|||
|
flood-opacity: initial !important;
|
|||
|
grid: initial !important;
|
|||
|
grid-area: initial !important;
|
|||
|
height: 20px !important;
|
|||
|
hyphens: initial !important;
|
|||
|
image-orientation: initial !important;
|
|||
|
image-rendering: initial !important;
|
|||
|
inline-size: initial !important;
|
|||
|
inset-block: initial !important;
|
|||
|
inset-inline: initial !important;
|
|||
|
isolation: initial !important;
|
|||
|
letter-spacing: initial !important;
|
|||
|
lighting-color: initial !important;
|
|||
|
line-break: initial !important;
|
|||
|
list-style: initial !important;
|
|||
|
margin-block: initial !important;
|
|||
|
margin-bottom: initial !important;
|
|||
|
margin-inline: initial !important;
|
|||
|
margin-left: 24px !important;
|
|||
|
margin-right: initial !important;
|
|||
|
margin-top: initial !important;
|
|||
|
marker: initial !important;
|
|||
|
mask: initial !important;
|
|||
|
mask-type: initial !important;
|
|||
|
max-block-size: initial !important;
|
|||
|
max-height: initial !important;
|
|||
|
max-inline-size: initial !important;
|
|||
|
max-width: initial !important;
|
|||
|
min-block-size: initial !important;
|
|||
|
min-height: initial !important;
|
|||
|
min-inline-size: initial !important;
|
|||
|
min-width: initial !important;
|
|||
|
mix-blend-mode: initial !important;
|
|||
|
object-fit: initial !important;
|
|||
|
object-position: initial !important;
|
|||
|
offset: initial !important;
|
|||
|
opacity: initial !important;
|
|||
|
order: initial !important;
|
|||
|
origin-trial-test-property: initial !important;
|
|||
|
orphans: initial !important;
|
|||
|
outline: initial !important;
|
|||
|
outline-offset: initial !important;
|
|||
|
overflow-anchor: initial !important;
|
|||
|
overflow-clip-margin: initial !important;
|
|||
|
overflow-wrap: initial !important;
|
|||
|
overflow: initial !important;
|
|||
|
overscroll-behavior-block: initial !important;
|
|||
|
overscroll-behavior-inline: initial !important;
|
|||
|
overscroll-behavior: initial !important;
|
|||
|
padding-block: initial !important;
|
|||
|
padding: initial !important;
|
|||
|
padding-inline: initial !important;
|
|||
|
page: initial !important;
|
|||
|
page-orientation: initial !important;
|
|||
|
paint-order: initial !important;
|
|||
|
perspective: initial !important;
|
|||
|
perspective-origin: initial !important;
|
|||
|
pointer-events: initial !important;
|
|||
|
position: initial !important;
|
|||
|
quotes: initial !important;
|
|||
|
r: initial !important;
|
|||
|
resize: initial !important;
|
|||
|
ruby-position: initial !important;
|
|||
|
rx: initial !important;
|
|||
|
ry: initial !important;
|
|||
|
scroll-behavior: initial !important;
|
|||
|
scroll-margin-block: initial !important;
|
|||
|
scroll-margin: initial !important;
|
|||
|
scroll-margin-inline: initial !important;
|
|||
|
scroll-padding-block: initial !important;
|
|||
|
scroll-padding: initial !important;
|
|||
|
scroll-padding-inline: initial !important;
|
|||
|
scroll-snap-align: initial !important;
|
|||
|
scroll-snap-stop: initial !important;
|
|||
|
scroll-snap-type: initial !important;
|
|||
|
scrollbar-gutter: initial !important;
|
|||
|
shape-image-threshold: initial !important;
|
|||
|
shape-margin: initial !important;
|
|||
|
shape-outside: initial !important;
|
|||
|
shape-rendering: initial !important;
|
|||
|
size: initial !important;
|
|||
|
speak: initial !important;
|
|||
|
stop-color: initial !important;
|
|||
|
stop-opacity: initial !important;
|
|||
|
stroke: initial !important;
|
|||
|
stroke-dasharray: initial !important;
|
|||
|
stroke-dashoffset: initial !important;
|
|||
|
stroke-linecap: initial !important;
|
|||
|
stroke-linejoin: initial !important;
|
|||
|
stroke-miterlimit: initial !important;
|
|||
|
stroke-opacity: initial !important;
|
|||
|
stroke-width: initial !important;
|
|||
|
tab-size: initial !important;
|
|||
|
table-layout: initial !important;
|
|||
|
text-align: initial !important;
|
|||
|
text-align-last: initial !important;
|
|||
|
text-anchor: initial !important;
|
|||
|
text-combine-upright: initial !important;
|
|||
|
text-decoration: initial !important;
|
|||
|
text-decoration-skip-ink: initial !important;
|
|||
|
text-emphasis: initial !important;
|
|||
|
text-emphasis-position: initial !important;
|
|||
|
text-indent: initial !important;
|
|||
|
text-overflow: initial !important;
|
|||
|
text-shadow: initial !important;
|
|||
|
text-size-adjust: initial !important;
|
|||
|
text-transform: initial !important;
|
|||
|
text-underline-offset: initial !important;
|
|||
|
text-underline-position: initial !important;
|
|||
|
touch-action: initial !important;
|
|||
|
transform: initial !important;
|
|||
|
transform-box: initial !important;
|
|||
|
transform-origin: initial !important;
|
|||
|
transform-style: initial !important;
|
|||
|
transition: initial !important;
|
|||
|
user-select: initial !important;
|
|||
|
vector-effect: initial !important;
|
|||
|
vertical-align: initial !important;
|
|||
|
visibility: initial !important;
|
|||
|
border-spacing: initial !important;
|
|||
|
-webkit-border-image: initial !important;
|
|||
|
-webkit-box-align: initial !important;
|
|||
|
-webkit-box-decoration-break: initial !important;
|
|||
|
-webkit-box-direction: initial !important;
|
|||
|
-webkit-box-flex: initial !important;
|
|||
|
-webkit-box-ordinal-group: initial !important;
|
|||
|
-webkit-box-orient: initial !important;
|
|||
|
-webkit-box-pack: initial !important;
|
|||
|
-webkit-box-reflect: initial !important;
|
|||
|
-webkit-highlight: initial !important;
|
|||
|
-webkit-hyphenate-character: initial !important;
|
|||
|
-webkit-line-break: initial !important;
|
|||
|
-webkit-line-clamp: initial !important;
|
|||
|
-webkit-mask-box-image: initial !important;
|
|||
|
-webkit-mask: initial !important;
|
|||
|
-webkit-mask-composite: initial !important;
|
|||
|
-webkit-perspective-origin-x: initial !important;
|
|||
|
-webkit-perspective-origin-y: initial !important;
|
|||
|
-webkit-print-color-adjust: initial !important;
|
|||
|
-webkit-rtl-ordering: initial !important;
|
|||
|
-webkit-ruby-position: initial !important;
|
|||
|
-webkit-tap-highlight-color: initial !important;
|
|||
|
-webkit-text-combine: initial !important;
|
|||
|
-webkit-text-decorations-in-effect: initial !important;
|
|||
|
-webkit-text-fill-color: initial !important;
|
|||
|
-webkit-text-security: initial !important;
|
|||
|
-webkit-text-stroke: initial !important;
|
|||
|
-webkit-transform-origin-x: initial !important;
|
|||
|
-webkit-transform-origin-y: initial !important;
|
|||
|
-webkit-transform-origin-z: initial !important;
|
|||
|
-webkit-user-drag: initial !important;
|
|||
|
-webkit-user-modify: initial !important;
|
|||
|
white-space: initial !important;
|
|||
|
widows: initial !important;
|
|||
|
width: 20px !important;
|
|||
|
will-change: initial !important;
|
|||
|
word-break: initial !important;
|
|||
|
word-spacing: initial !important;
|
|||
|
x: initial !important;
|
|||
|
y: initial !important;
|
|||
|
z-index: initial !important;
|
|||
|
"
|
|||
|
>
|
|||
|
<path
|
|||
|
d="M8.25.004a8 8 0 0 1 0 15.992L8 16a.5.5 0 0 1-.09-.992L8 15a7 7 0 0 0 .24-13.996L8 1a.5.5 0 0 1-.09-.992L8 0l.25.004z"
|
|||
|
>
|
|||
|
<animateTransform
|
|||
|
attributeName="transform"
|
|||
|
attributeType="XML"
|
|||
|
dur="800ms"
|
|||
|
from="0 8 8"
|
|||
|
repeatCount="indefinite"
|
|||
|
to="360 8 8"
|
|||
|
type="rotate"
|
|||
|
></animateTransform>
|
|||
|
</path>
|
|||
|
</svg>
|
|||
|
<div
|
|||
|
style="
|
|||
|
color: inherit !important;
|
|||
|
font: inherit !important;
|
|||
|
font-feature-settings: initial !important;
|
|||
|
font-kerning: initial !important;
|
|||
|
font-optical-sizing: initial !important;
|
|||
|
font-palette: initial !important;
|
|||
|
font-synthesis: initial !important;
|
|||
|
font-variation-settings: initial !important;
|
|||
|
forced-color-adjust: initial !important;
|
|||
|
text-orientation: initial !important;
|
|||
|
text-rendering: initial !important;
|
|||
|
-webkit-font-smoothing: initial !important;
|
|||
|
-webkit-locale: initial !important;
|
|||
|
-webkit-text-orientation: initial !important;
|
|||
|
-webkit-writing-mode: initial !important;
|
|||
|
writing-mode: initial !important;
|
|||
|
zoom: initial !important;
|
|||
|
accent-color: initial !important;
|
|||
|
place-content: initial !important;
|
|||
|
place-items: initial !important;
|
|||
|
place-self: initial !important;
|
|||
|
alignment-baseline: initial !important;
|
|||
|
animation: initial !important;
|
|||
|
app-region: initial !important;
|
|||
|
appearance: initial !important;
|
|||
|
aspect-ratio: initial !important;
|
|||
|
backdrop-filter: initial !important;
|
|||
|
backface-visibility: initial !important;
|
|||
|
background: initial !important;
|
|||
|
background-blend-mode: initial !important;
|
|||
|
baseline-shift: initial !important;
|
|||
|
block-size: initial !important;
|
|||
|
border-block: initial !important;
|
|||
|
border: initial !important;
|
|||
|
border-radius: initial !important;
|
|||
|
border-collapse: initial !important;
|
|||
|
border-end-end-radius: initial !important;
|
|||
|
border-end-start-radius: initial !important;
|
|||
|
border-inline: initial !important;
|
|||
|
border-start-end-radius: initial !important;
|
|||
|
border-start-start-radius: initial !important;
|
|||
|
inset: initial !important;
|
|||
|
box-shadow: initial !important;
|
|||
|
box-sizing: initial !important;
|
|||
|
break-after: initial !important;
|
|||
|
break-before: initial !important;
|
|||
|
break-inside: initial !important;
|
|||
|
buffered-rendering: initial !important;
|
|||
|
caption-side: initial !important;
|
|||
|
caret-color: initial !important;
|
|||
|
clear: initial !important;
|
|||
|
clip: initial !important;
|
|||
|
clip-path: initial !important;
|
|||
|
clip-rule: initial !important;
|
|||
|
color-interpolation: initial !important;
|
|||
|
color-interpolation-filters: initial !important;
|
|||
|
color-rendering: initial !important;
|
|||
|
color-scheme: initial !important;
|
|||
|
columns: initial !important;
|
|||
|
column-fill: initial !important;
|
|||
|
gap: initial !important;
|
|||
|
column-rule: initial !important;
|
|||
|
column-span: initial !important;
|
|||
|
contain: initial !important;
|
|||
|
contain-intrinsic-block-size: initial !important;
|
|||
|
contain-intrinsic-size: initial !important;
|
|||
|
contain-intrinsic-inline-size: initial !important;
|
|||
|
content: initial !important;
|
|||
|
content-visibility: initial !important;
|
|||
|
counter-increment: initial !important;
|
|||
|
counter-reset: initial !important;
|
|||
|
counter-set: initial !important;
|
|||
|
cursor: initial !important;
|
|||
|
cx: initial !important;
|
|||
|
cy: initial !important;
|
|||
|
d: initial !important;
|
|||
|
display: initial !important;
|
|||
|
dominant-baseline: initial !important;
|
|||
|
empty-cells: initial !important;
|
|||
|
fill: initial !important;
|
|||
|
fill-opacity: initial !important;
|
|||
|
fill-rule: initial !important;
|
|||
|
filter: initial !important;
|
|||
|
flex: 1 1 0% !important;
|
|||
|
flex-flow: initial !important;
|
|||
|
float: initial !important;
|
|||
|
flood-color: initial !important;
|
|||
|
flood-opacity: initial !important;
|
|||
|
grid: initial !important;
|
|||
|
grid-area: initial !important;
|
|||
|
height: initial !important;
|
|||
|
hyphens: initial !important;
|
|||
|
image-orientation: initial !important;
|
|||
|
image-rendering: initial !important;
|
|||
|
inline-size: initial !important;
|
|||
|
inset-block: initial !important;
|
|||
|
inset-inline: initial !important;
|
|||
|
isolation: initial !important;
|
|||
|
letter-spacing: initial !important;
|
|||
|
lighting-color: initial !important;
|
|||
|
line-break: initial !important;
|
|||
|
list-style: initial !important;
|
|||
|
margin-block: initial !important;
|
|||
|
margin: initial !important;
|
|||
|
margin-inline: initial !important;
|
|||
|
marker: initial !important;
|
|||
|
mask: initial !important;
|
|||
|
mask-type: initial !important;
|
|||
|
max-block-size: initial !important;
|
|||
|
max-height: initial !important;
|
|||
|
max-inline-size: initial !important;
|
|||
|
max-width: initial !important;
|
|||
|
min-block-size: initial !important;
|
|||
|
min-height: initial !important;
|
|||
|
min-inline-size: initial !important;
|
|||
|
min-width: initial !important;
|
|||
|
mix-blend-mode: initial !important;
|
|||
|
object-fit: initial !important;
|
|||
|
object-position: initial !important;
|
|||
|
offset: initial !important;
|
|||
|
opacity: initial !important;
|
|||
|
order: initial !important;
|
|||
|
origin-trial-test-property: initial !important;
|
|||
|
orphans: initial !important;
|
|||
|
outline: initial !important;
|
|||
|
outline-offset: initial !important;
|
|||
|
overflow-anchor: initial !important;
|
|||
|
overflow-clip-margin: initial !important;
|
|||
|
overflow-wrap: initial !important;
|
|||
|
overflow: initial !important;
|
|||
|
overscroll-behavior-block: initial !important;
|
|||
|
overscroll-behavior-inline: initial !important;
|
|||
|
overscroll-behavior: initial !important;
|
|||
|
padding-block: initial !important;
|
|||
|
padding: 0px 24px !important;
|
|||
|
padding-inline: initial !important;
|
|||
|
page: initial !important;
|
|||
|
page-orientation: initial !important;
|
|||
|
paint-order: initial !important;
|
|||
|
perspective: initial !important;
|
|||
|
perspective-origin: initial !important;
|
|||
|
pointer-events: initial !important;
|
|||
|
position: initial !important;
|
|||
|
quotes: initial !important;
|
|||
|
r: initial !important;
|
|||
|
resize: initial !important;
|
|||
|
ruby-position: initial !important;
|
|||
|
rx: initial !important;
|
|||
|
ry: initial !important;
|
|||
|
scroll-behavior: initial !important;
|
|||
|
scroll-margin-block: initial !important;
|
|||
|
scroll-margin: initial !important;
|
|||
|
scroll-margin-inline: initial !important;
|
|||
|
scroll-padding-block: initial !important;
|
|||
|
scroll-padding: initial !important;
|
|||
|
scroll-padding-inline: initial !important;
|
|||
|
scroll-snap-align: initial !important;
|
|||
|
scroll-snap-stop: initial !important;
|
|||
|
scroll-snap-type: initial !important;
|
|||
|
scrollbar-gutter: initial !important;
|
|||
|
shape-image-threshold: initial !important;
|
|||
|
shape-margin: initial !important;
|
|||
|
shape-outside: initial !important;
|
|||
|
shape-rendering: initial !important;
|
|||
|
size: initial !important;
|
|||
|
speak: initial !important;
|
|||
|
stop-color: initial !important;
|
|||
|
stop-opacity: initial !important;
|
|||
|
stroke: initial !important;
|
|||
|
stroke-dasharray: initial !important;
|
|||
|
stroke-dashoffset: initial !important;
|
|||
|
stroke-linecap: initial !important;
|
|||
|
stroke-linejoin: initial !important;
|
|||
|
stroke-miterlimit: initial !important;
|
|||
|
stroke-opacity: initial !important;
|
|||
|
stroke-width: initial !important;
|
|||
|
tab-size: initial !important;
|
|||
|
table-layout: initial !important;
|
|||
|
text-align: initial !important;
|
|||
|
text-align-last: initial !important;
|
|||
|
text-anchor: initial !important;
|
|||
|
text-combine-upright: initial !important;
|
|||
|
text-decoration: initial !important;
|
|||
|
text-decoration-skip-ink: initial !important;
|
|||
|
text-emphasis: initial !important;
|
|||
|
text-emphasis-position: initial !important;
|
|||
|
text-indent: initial !important;
|
|||
|
text-overflow: initial !important;
|
|||
|
text-shadow: initial !important;
|
|||
|
text-size-adjust: initial !important;
|
|||
|
text-transform: initial !important;
|
|||
|
text-underline-offset: initial !important;
|
|||
|
text-underline-position: initial !important;
|
|||
|
touch-action: initial !important;
|
|||
|
transform: initial !important;
|
|||
|
transform-box: initial !important;
|
|||
|
transform-origin: initial !important;
|
|||
|
transform-style: initial !important;
|
|||
|
transition: initial !important;
|
|||
|
user-select: initial !important;
|
|||
|
vector-effect: initial !important;
|
|||
|
vertical-align: initial !important;
|
|||
|
visibility: initial !important;
|
|||
|
border-spacing: initial !important;
|
|||
|
-webkit-border-image: initial !important;
|
|||
|
-webkit-box-align: initial !important;
|
|||
|
-webkit-box-decoration-break: initial !important;
|
|||
|
-webkit-box-direction: initial !important;
|
|||
|
-webkit-box-flex: initial !important;
|
|||
|
-webkit-box-ordinal-group: initial !important;
|
|||
|
-webkit-box-orient: initial !important;
|
|||
|
-webkit-box-pack: initial !important;
|
|||
|
-webkit-box-reflect: initial !important;
|
|||
|
-webkit-highlight: initial !important;
|
|||
|
-webkit-hyphenate-character: initial !important;
|
|||
|
-webkit-line-break: initial !important;
|
|||
|
-webkit-line-clamp: initial !important;
|
|||
|
-webkit-mask-box-image: initial !important;
|
|||
|
-webkit-mask: initial !important;
|
|||
|
-webkit-mask-composite: initial !important;
|
|||
|
-webkit-perspective-origin-x: initial !important;
|
|||
|
-webkit-perspective-origin-y: initial !important;
|
|||
|
-webkit-print-color-adjust: initial !important;
|
|||
|
-webkit-rtl-ordering: initial !important;
|
|||
|
-webkit-ruby-position: initial !important;
|
|||
|
-webkit-tap-highlight-color: initial !important;
|
|||
|
-webkit-text-combine: initial !important;
|
|||
|
-webkit-text-decorations-in-effect: initial !important;
|
|||
|
-webkit-text-fill-color: initial !important;
|
|||
|
-webkit-text-security: initial !important;
|
|||
|
-webkit-text-stroke: initial !important;
|
|||
|
-webkit-transform-origin-x: initial !important;
|
|||
|
-webkit-transform-origin-y: initial !important;
|
|||
|
-webkit-transform-origin-z: initial !important;
|
|||
|
-webkit-user-drag: initial !important;
|
|||
|
-webkit-user-modify: initial !important;
|
|||
|
white-space: initial !important;
|
|||
|
widows: initial !important;
|
|||
|
width: initial !important;
|
|||
|
will-change: initial !important;
|
|||
|
word-break: initial !important;
|
|||
|
word-spacing: initial !important;
|
|||
|
x: initial !important;
|
|||
|
y: initial !important;
|
|||
|
z-index: initial !important;
|
|||
|
"
|
|||
|
>
|
|||
|
Saving...
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div
|
|||
|
class="webext-omnivore-backdrop"
|
|||
|
style="
|
|||
|
color: initial !important;
|
|||
|
font: initial !important;
|
|||
|
font-feature-settings: initial !important;
|
|||
|
font-kerning: initial !important;
|
|||
|
font-optical-sizing: initial !important;
|
|||
|
font-palette: initial !important;
|
|||
|
font-synthesis: initial !important;
|
|||
|
font-variation-settings: initial !important;
|
|||
|
forced-color-adjust: initial !important;
|
|||
|
text-orientation: initial !important;
|
|||
|
text-rendering: initial !important;
|
|||
|
-webkit-font-smoothing: initial !important;
|
|||
|
-webkit-locale: initial !important;
|
|||
|
-webkit-text-orientation: initial !important;
|
|||
|
-webkit-writing-mode: initial !important;
|
|||
|
writing-mode: initial !important;
|
|||
|
zoom: initial !important;
|
|||
|
accent-color: initial !important;
|
|||
|
place-content: initial !important;
|
|||
|
place-items: initial !important;
|
|||
|
place-self: initial !important;
|
|||
|
alignment-baseline: initial !important;
|
|||
|
animation: initial !important;
|
|||
|
app-region: initial !important;
|
|||
|
appearance: initial !important;
|
|||
|
aspect-ratio: initial !important;
|
|||
|
backdrop-filter: blur(4px) !important;
|
|||
|
backface-visibility: initial !important;
|
|||
|
background: rgb(255, 255, 255) !important;
|
|||
|
background-blend-mode: initial !important;
|
|||
|
baseline-shift: initial !important;
|
|||
|
block-size: initial !important;
|
|||
|
border-block: initial !important;
|
|||
|
border: initial !important;
|
|||
|
border-radius: initial !important;
|
|||
|
border-collapse: initial !important;
|
|||
|
border-end-end-radius: initial !important;
|
|||
|
border-end-start-radius: initial !important;
|
|||
|
border-inline: initial !important;
|
|||
|
border-start-end-radius: initial !important;
|
|||
|
border-start-start-radius: initial !important;
|
|||
|
inset: 0px !important;
|
|||
|
box-shadow: initial !important;
|
|||
|
box-sizing: initial !important;
|
|||
|
break-after: initial !important;
|
|||
|
break-before: initial !important;
|
|||
|
break-inside: initial !important;
|
|||
|
buffered-rendering: initial !important;
|
|||
|
caption-side: initial !important;
|
|||
|
caret-color: initial !important;
|
|||
|
clear: initial !important;
|
|||
|
clip: initial !important;
|
|||
|
clip-path: initial !important;
|
|||
|
clip-rule: initial !important;
|
|||
|
color-interpolation: initial !important;
|
|||
|
color-interpolation-filters: initial !important;
|
|||
|
color-rendering: initial !important;
|
|||
|
color-scheme: initial !important;
|
|||
|
columns: initial !important;
|
|||
|
column-fill: initial !important;
|
|||
|
gap: initial !important;
|
|||
|
column-rule: initial !important;
|
|||
|
column-span: initial !important;
|
|||
|
contain: initial !important;
|
|||
|
contain-intrinsic-block-size: initial !important;
|
|||
|
contain-intrinsic-size: initial !important;
|
|||
|
contain-intrinsic-inline-size: initial !important;
|
|||
|
content: initial !important;
|
|||
|
content-visibility: initial !important;
|
|||
|
counter-increment: initial !important;
|
|||
|
counter-reset: initial !important;
|
|||
|
counter-set: initial !important;
|
|||
|
cursor: initial !important;
|
|||
|
cx: initial !important;
|
|||
|
cy: initial !important;
|
|||
|
d: initial !important;
|
|||
|
display: initial !important;
|
|||
|
dominant-baseline: initial !important;
|
|||
|
empty-cells: initial !important;
|
|||
|
fill: initial !important;
|
|||
|
fill-opacity: initial !important;
|
|||
|
fill-rule: initial !important;
|
|||
|
filter: initial !important;
|
|||
|
flex: initial !important;
|
|||
|
flex-flow: initial !important;
|
|||
|
float: initial !important;
|
|||
|
flood-color: initial !important;
|
|||
|
flood-opacity: initial !important;
|
|||
|
grid: initial !important;
|
|||
|
grid-area: initial !important;
|
|||
|
height: initial !important;
|
|||
|
hyphens: initial !important;
|
|||
|
image-orientation: initial !important;
|
|||
|
image-rendering: initial !important;
|
|||
|
inline-size: initial !important;
|
|||
|
inset-block: initial !important;
|
|||
|
inset-inline: initial !important;
|
|||
|
isolation: initial !important;
|
|||
|
letter-spacing: initial !important;
|
|||
|
lighting-color: initial !important;
|
|||
|
line-break: initial !important;
|
|||
|
list-style: initial !important;
|
|||
|
margin-block: initial !important;
|
|||
|
margin: initial !important;
|
|||
|
margin-inline: initial !important;
|
|||
|
marker: initial !important;
|
|||
|
mask: initial !important;
|
|||
|
mask-type: initial !important;
|
|||
|
max-block-size: initial !important;
|
|||
|
max-height: initial !important;
|
|||
|
max-inline-size: initial !important;
|
|||
|
max-width: initial !important;
|
|||
|
min-block-size: initial !important;
|
|||
|
min-height: initial !important;
|
|||
|
min-inline-size: initial !important;
|
|||
|
min-width: initial !important;
|
|||
|
mix-blend-mode: initial !important;
|
|||
|
object-fit: initial !important;
|
|||
|
object-position: initial !important;
|
|||
|
offset: initial !important;
|
|||
|
opacity: 0 !important;
|
|||
|
order: initial !important;
|
|||
|
origin-trial-test-property: initial !important;
|
|||
|
orphans: initial !important;
|
|||
|
outline: initial !important;
|
|||
|
outline-offset: initial !important;
|
|||
|
overflow-anchor: initial !important;
|
|||
|
overflow-clip-margin: initial !important;
|
|||
|
overflow-wrap: initial !important;
|
|||
|
overflow: initial !important;
|
|||
|
overscroll-behavior-block: initial !important;
|
|||
|
overscroll-behavior-inline: initial !important;
|
|||
|
overscroll-behavior: initial !important;
|
|||
|
padding-block: initial !important;
|
|||
|
padding: initial !important;
|
|||
|
padding-inline: initial !important;
|
|||
|
page: initial !important;
|
|||
|
page-orientation: initial !important;
|
|||
|
paint-order: initial !important;
|
|||
|
perspective: initial !important;
|
|||
|
perspective-origin: initial !important;
|
|||
|
pointer-events: initial !important;
|
|||
|
position: fixed !important;
|
|||
|
quotes: initial !important;
|
|||
|
r: initial !important;
|
|||
|
resize: initial !important;
|
|||
|
ruby-position: initial !important;
|
|||
|
rx: initial !important;
|
|||
|
ry: initial !important;
|
|||
|
scroll-behavior: initial !important;
|
|||
|
scroll-margin-block: initial !important;
|
|||
|
scroll-margin: initial !important;
|
|||
|
scroll-margin-inline: initial !important;
|
|||
|
scroll-padding-block: initial !important;
|
|||
|
scroll-padding: initial !important;
|
|||
|
scroll-padding-inline: initial !important;
|
|||
|
scroll-snap-align: initial !important;
|
|||
|
scroll-snap-stop: initial !important;
|
|||
|
scroll-snap-type: initial !important;
|
|||
|
scrollbar-gutter: initial !important;
|
|||
|
shape-image-threshold: initial !important;
|
|||
|
shape-margin: initial !important;
|
|||
|
shape-outside: initial !important;
|
|||
|
shape-rendering: initial !important;
|
|||
|
size: initial !important;
|
|||
|
speak: initial !important;
|
|||
|
stop-color: initial !important;
|
|||
|
stop-opacity: initial !important;
|
|||
|
stroke: initial !important;
|
|||
|
stroke-dasharray: initial !important;
|
|||
|
stroke-dashoffset: initial !important;
|
|||
|
stroke-linecap: initial !important;
|
|||
|
stroke-linejoin: initial !important;
|
|||
|
stroke-miterlimit: initial !important;
|
|||
|
stroke-opacity: initial !important;
|
|||
|
stroke-width: initial !important;
|
|||
|
tab-size: initial !important;
|
|||
|
table-layout: initial !important;
|
|||
|
text-align: initial !important;
|
|||
|
text-align-last: initial !important;
|
|||
|
text-anchor: initial !important;
|
|||
|
text-combine-upright: initial !important;
|
|||
|
text-decoration: initial !important;
|
|||
|
text-decoration-skip-ink: initial !important;
|
|||
|
text-emphasis: initial !important;
|
|||
|
text-emphasis-position: initial !important;
|
|||
|
text-indent: initial !important;
|
|||
|
text-overflow: initial !important;
|
|||
|
text-shadow: initial !important;
|
|||
|
text-size-adjust: initial !important;
|
|||
|
text-transform: initial !important;
|
|||
|
text-underline-offset: initial !important;
|
|||
|
text-underline-position: initial !important;
|
|||
|
touch-action: initial !important;
|
|||
|
transform: initial !important;
|
|||
|
transform-box: initial !important;
|
|||
|
transform-origin: initial !important;
|
|||
|
transform-style: initial !important;
|
|||
|
transition: opacity 0.3s ease 0s !important;
|
|||
|
user-select: initial !important;
|
|||
|
vector-effect: initial !important;
|
|||
|
vertical-align: initial !important;
|
|||
|
visibility: initial !important;
|
|||
|
border-spacing: initial !important;
|
|||
|
-webkit-border-image: initial !important;
|
|||
|
-webkit-box-align: initial !important;
|
|||
|
-webkit-box-decoration-break: initial !important;
|
|||
|
-webkit-box-direction: initial !important;
|
|||
|
-webkit-box-flex: initial !important;
|
|||
|
-webkit-box-ordinal-group: initial !important;
|
|||
|
-webkit-box-orient: initial !important;
|
|||
|
-webkit-box-pack: initial !important;
|
|||
|
-webkit-box-reflect: initial !important;
|
|||
|
-webkit-highlight: initial !important;
|
|||
|
-webkit-hyphenate-character: initial !important;
|
|||
|
-webkit-line-break: initial !important;
|
|||
|
-webkit-line-clamp: initial !important;
|
|||
|
-webkit-mask-box-image: initial !important;
|
|||
|
-webkit-mask: initial !important;
|
|||
|
-webkit-mask-composite: initial !important;
|
|||
|
-webkit-perspective-origin-x: initial !important;
|
|||
|
-webkit-perspective-origin-y: initial !important;
|
|||
|
-webkit-print-color-adjust: initial !important;
|
|||
|
-webkit-rtl-ordering: initial !important;
|
|||
|
-webkit-ruby-position: initial !important;
|
|||
|
-webkit-tap-highlight-color: initial !important;
|
|||
|
-webkit-text-combine: initial !important;
|
|||
|
-webkit-text-decorations-in-effect: initial !important;
|
|||
|
-webkit-text-fill-color: initial !important;
|
|||
|
-webkit-text-security: initial !important;
|
|||
|
-webkit-text-stroke: initial !important;
|
|||
|
-webkit-transform-origin-x: initial !important;
|
|||
|
-webkit-transform-origin-y: initial !important;
|
|||
|
-webkit-transform-origin-z: initial !important;
|
|||
|
-webkit-user-drag: initial !important;
|
|||
|
-webkit-user-modify: initial !important;
|
|||
|
white-space: initial !important;
|
|||
|
widows: initial !important;
|
|||
|
width: initial !important;
|
|||
|
will-change: initial !important;
|
|||
|
word-break: initial !important;
|
|||
|
word-spacing: initial !important;
|
|||
|
x: initial !important;
|
|||
|
y: initial !important;
|
|||
|
z-index: 99999 !important;
|
|||
|
"
|
|||
|
></div>
|
|||
|
</body>
|
|||
|
</html>
|