feat: add vaapi path for video wallpapers on generic gpus
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Failing after 32s
Rust / build (pull_request) Successful in 52s
Python / verify (pull_request) Failing after 1m23s
Rust / fmt (pull_request) Successful in 51s
Rust / clippy (pull_request) Successful in 1m2s
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Failing after 32s
Rust / build (pull_request) Successful in 52s
Python / verify (pull_request) Failing after 1m23s
Rust / fmt (pull_request) Successful in 51s
Rust / clippy (pull_request) Successful in 1m2s
This commit is contained in:
@@ -1,8 +1,3 @@
|
||||
wl_proto(wlp-layer-shell wlr-layer-shell-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}/protocols")
|
||||
wl_proto(wlp-viewporter viewporter "${WAYLAND_PROTOCOLS}/stable/viewporter")
|
||||
wl_proto(wlp-fractional-scale fractional-scale-v1 "${WAYLAND_PROTOCOLS}/staging/fractional-scale")
|
||||
wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell")
|
||||
|
||||
qml_module(ZShell-internal
|
||||
URI ZShell.Internal
|
||||
INCLUDE_PREFIX internal
|
||||
@@ -13,16 +8,11 @@ qml_module(ZShell-internal
|
||||
circularindicatormanager.hpp circularindicatormanager.cpp
|
||||
circularbuffer.hpp circularbuffer.cpp
|
||||
arcgauge.hpp arcgauge.cpp
|
||||
wallpaperimage.hpp wallpaperimage.cpp
|
||||
wallpaperservice.hpp wallpaperservice.cpp
|
||||
wallpapersurface.hpp wallpapersurface.cpp
|
||||
wallpaperobject.hpp wallpaperobject.cpp
|
||||
lidwatcher.hpp lidwatcher.cpp
|
||||
visualizerbars.hpp visualizerbars.cpp
|
||||
linearindicatormanager.hpp linearindicatormanager.cpp
|
||||
strokecanvasitem.hpp strokecanvasitem.cpp
|
||||
strokecanvasrenderer.hpp strokecanvasrenderer.cpp
|
||||
videoplayer.hpp videoplayer.cpp
|
||||
cursor.hpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
@@ -34,39 +24,8 @@ qml_module(ZShell-internal
|
||||
Qt::DBus
|
||||
Qt::CanvasPainter
|
||||
Qt::GuiPrivate
|
||||
wlp-layer-shell
|
||||
wlp-xdg-shell
|
||||
wlp-viewporter
|
||||
wlp-fractional-scale
|
||||
PkgConfig::EGL
|
||||
PkgConfig::WAYLAND_EGL
|
||||
PkgConfig::GL
|
||||
PkgConfig::MPV
|
||||
PkgConfig::AVCODEC
|
||||
PkgConfig::AVFORMAT
|
||||
PkgConfig::AVUTIL
|
||||
ZShell-config
|
||||
)
|
||||
|
||||
qt_add_resources(ZShell-internal "wallpaper_shaders"
|
||||
PREFIX "/"
|
||||
FILES
|
||||
shaders/wallpaper.vert
|
||||
shaders/wallpaper.frag
|
||||
shaders/wallpaper_video.frag
|
||||
shaders/wallpaper_trans_bowtie.frag
|
||||
shaders/wallpaper_trans_fade.frag
|
||||
shaders/wallpaper_trans_linear_blur.frag
|
||||
shaders/wallpaper_trans_window_slices.frag
|
||||
shaders/wallpaper_trans_wipe_right.frag
|
||||
shaders/wallpaper_trans_wipe_down.frag
|
||||
shaders/wallpaper_trans_tiles_wave.frag
|
||||
shaders/wallpaper_trans_split_slide_in_horizontal.frag
|
||||
shaders/wallpaper_trans_hexagonalize.frag
|
||||
shaders/wallpaper_trans_doom_melt.frag
|
||||
)
|
||||
|
||||
if(CUDAToolkit_FOUND)
|
||||
target_link_libraries(ZShell-internal PRIVATE CUDA::cuda_driver)
|
||||
target_compile_definitions(ZShell-internal PRIVATE ZHAVE_VIDEO=1)
|
||||
endif()
|
||||
|
||||
@@ -1,407 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_layer_shell_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2017 Drew DeVault
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_layer_shell_v1" version="5">
|
||||
<description summary="create surfaces that are layers of the desktop">
|
||||
Clients can use this interface to assign the surface_layer role to
|
||||
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||
rendered with a defined z-depth respective to each other. They may also be
|
||||
anchored to the edges and corners of a screen and specify input handling
|
||||
semantics. This interface should be suitable for the implementation of
|
||||
many desktop shell components, and a broad number of other applications
|
||||
that interact with the desktop.
|
||||
</description>
|
||||
|
||||
<request name="get_layer_surface">
|
||||
<description summary="create a layer_surface from a surface">
|
||||
Create a layer surface for an existing surface. This assigns the role of
|
||||
layer_surface, or raises a protocol error if another role is already
|
||||
assigned.
|
||||
|
||||
Creating a layer surface from a wl_surface which has a buffer attached
|
||||
or committed is a client error, and any attempts by a client to attach
|
||||
or manipulate a buffer prior to the first layer_surface.configure call
|
||||
must also be treated as errors.
|
||||
|
||||
After creating a layer_surface object and setting it up, the client
|
||||
must perform an initial commit without any buffer attached.
|
||||
The compositor will reply with a layer_surface.configure event.
|
||||
The client must acknowledge it and is then allowed to attach a buffer
|
||||
to map the surface.
|
||||
|
||||
You may pass NULL for output to allow the compositor to decide which
|
||||
output to use. Generally this will be the one that the user most
|
||||
recently interacted with.
|
||||
|
||||
Clients can specify a namespace that defines the purpose of the layer
|
||||
surface.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
|
||||
<arg name="namespace_" type="string" summary="namespace for the layer surface"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="role" value="0" summary="wl_surface has another role"/>
|
||||
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
|
||||
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
|
||||
</enum>
|
||||
|
||||
<enum name="layer">
|
||||
<description summary="available layers for surfaces">
|
||||
These values indicate which layers a surface can be rendered in. They
|
||||
are ordered by z depth, bottom-most first. Traditional shell surfaces
|
||||
will typically be rendered between the bottom and top layers.
|
||||
Fullscreen shell surfaces are typically rendered at the top layer.
|
||||
Multiple surfaces can share a single layer, and ordering within a
|
||||
single layer is undefined.
|
||||
</description>
|
||||
|
||||
<entry name="background" value="0"/>
|
||||
<entry name="bottom" value="1"/>
|
||||
<entry name="top" value="2"/>
|
||||
<entry name="overlay" value="3"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<request name="destroy" type="destructor" since="3">
|
||||
<description summary="destroy the layer_shell object">
|
||||
This request indicates that the client will not use the layer_shell
|
||||
object any more. Objects that have been created through this instance
|
||||
are not affected.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_layer_surface_v1" version="5">
|
||||
<description summary="layer metadata interface">
|
||||
An interface that may be implemented by a wl_surface, for surfaces that
|
||||
are designed to be rendered as a layer of a stacked desktop-like
|
||||
environment.
|
||||
|
||||
Layer surface state (layer, size, anchor, exclusive zone,
|
||||
margin, interactivity) is double-buffered, and will be applied at the
|
||||
time wl_surface.commit of the corresponding wl_surface is called.
|
||||
|
||||
Attaching a null buffer to a layer surface unmaps it.
|
||||
|
||||
Unmapping a layer_surface means that the surface cannot be shown by the
|
||||
compositor until it is explicitly mapped again. The layer_surface
|
||||
returns to the state it had right after layer_shell.get_layer_surface.
|
||||
The client can re-map the surface by performing a commit without any
|
||||
buffer attached, waiting for a configure event and handling it as usual.
|
||||
</description>
|
||||
|
||||
<request name="set_size">
|
||||
<description summary="sets the size of the surface">
|
||||
Sets the size of the surface in surface-local coordinates. The
|
||||
compositor will display the surface centered with respect to its
|
||||
anchors.
|
||||
|
||||
If you pass 0 for either value, the compositor will assign it and
|
||||
inform you of the assignment in the configure event. You must set your
|
||||
anchor to opposite edges in the dimensions you omit; not doing so is a
|
||||
protocol error. Both values are 0 by default.
|
||||
|
||||
Size is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</request>
|
||||
|
||||
<request name="set_anchor">
|
||||
<description summary="configures the anchor point of the surface">
|
||||
Requests that the compositor anchor the surface to the specified edges
|
||||
and corners. If two orthogonal edges are specified (e.g. 'top' and
|
||||
'left'), then the anchor point will be the intersection of the edges
|
||||
(e.g. the top left corner of the output); otherwise the anchor point
|
||||
will be centered on that edge, or in the center if none is specified.
|
||||
|
||||
Anchor is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="anchor" type="uint" enum="anchor"/>
|
||||
</request>
|
||||
|
||||
<request name="set_exclusive_zone">
|
||||
<description summary="configures the exclusive geometry of this surface">
|
||||
Requests that the compositor avoids occluding an area with other
|
||||
surfaces. The compositor's use of this information is
|
||||
implementation-dependent - do not assume that this region will not
|
||||
actually be occluded.
|
||||
|
||||
A positive value is only meaningful if the surface is anchored to one
|
||||
edge or an edge and both perpendicular edges. If the surface is not
|
||||
anchored, anchored to only two perpendicular edges (a corner), anchored
|
||||
to only two parallel edges or anchored to all edges, a positive value
|
||||
will be treated the same as zero.
|
||||
|
||||
A positive zone is the distance from the edge in surface-local
|
||||
coordinates to consider exclusive.
|
||||
|
||||
Surfaces that do not wish to have an exclusive zone may instead specify
|
||||
how they should interact with surfaces that do. If set to zero, the
|
||||
surface indicates that it would like to be moved to avoid occluding
|
||||
surfaces with a positive exclusive zone. If set to -1, the surface
|
||||
indicates that it would not like to be moved to accommodate for other
|
||||
surfaces, and the compositor should extend it all the way to the edges
|
||||
it is anchored to.
|
||||
|
||||
For example, a panel might set its exclusive zone to 10, so that
|
||||
maximized shell surfaces are not shown on top of it. A notification
|
||||
might set its exclusive zone to 0, so that it is moved to avoid
|
||||
occluding the panel, but shell surfaces are shown underneath it. A
|
||||
wallpaper or lock screen might set their exclusive zone to -1, so that
|
||||
they stretch below or over the panel.
|
||||
|
||||
The default value is 0.
|
||||
|
||||
Exclusive zone is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="zone" type="int"/>
|
||||
</request>
|
||||
|
||||
<request name="set_margin">
|
||||
<description summary="sets a margin from the anchor point">
|
||||
Requests that the surface be placed some distance away from the anchor
|
||||
point on the output, in surface-local coordinates. Setting this value
|
||||
for edges you are not anchored to has no effect.
|
||||
|
||||
The exclusive zone includes the margin.
|
||||
|
||||
Margin is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="top" type="int"/>
|
||||
<arg name="right" type="int"/>
|
||||
<arg name="bottom" type="int"/>
|
||||
<arg name="left" type="int"/>
|
||||
</request>
|
||||
|
||||
<enum name="keyboard_interactivity">
|
||||
<description summary="types of keyboard interaction possible for a layer shell surface">
|
||||
Types of keyboard interaction possible for layer shell surfaces. The
|
||||
rationale for this is twofold: (1) some applications are not interested
|
||||
in keyboard events and not allowing them to be focused can improve the
|
||||
desktop experience; (2) some applications will want to take exclusive
|
||||
keyboard focus.
|
||||
</description>
|
||||
|
||||
<entry name="none" value="0">
|
||||
<description summary="no keyboard focus is possible">
|
||||
This value indicates that this surface is not interested in keyboard
|
||||
events and the compositor should never assign it the keyboard focus.
|
||||
|
||||
This is the default value, set for newly created layer shell surfaces.
|
||||
|
||||
This is useful for e.g. desktop widgets that display information or
|
||||
only have interaction with non-keyboard input devices.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="exclusive" value="1">
|
||||
<description summary="request exclusive keyboard focus">
|
||||
Request exclusive keyboard focus if this surface is above the shell surface layer.
|
||||
|
||||
For the top and overlay layers, the seat will always give
|
||||
exclusive keyboard focus to the top-most layer which has keyboard
|
||||
interactivity set to exclusive. If this layer contains multiple
|
||||
surfaces with keyboard interactivity set to exclusive, the compositor
|
||||
determines the one receiving keyboard events in an implementation-
|
||||
defined manner. In this case, no guarantee is made when this surface
|
||||
will receive keyboard focus (if ever).
|
||||
|
||||
For the bottom and background layers, the compositor is allowed to use
|
||||
normal focus semantics.
|
||||
|
||||
This setting is mainly intended for applications that need to ensure
|
||||
they receive all keyboard events, such as a lock screen or a password
|
||||
prompt.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="on_demand" value="2" since="4">
|
||||
<description summary="request regular keyboard focus semantics">
|
||||
This requests the compositor to allow this surface to be focused and
|
||||
unfocused by the user in an implementation-defined manner. The user
|
||||
should be able to unfocus this surface even regardless of the layer
|
||||
it is on.
|
||||
|
||||
Typically, the compositor will want to use its normal mechanism to
|
||||
manage keyboard focus between layer shell surfaces with this setting
|
||||
and regular toplevels on the desktop layer (e.g. click to focus).
|
||||
Nevertheless, it is possible for a compositor to require a special
|
||||
interaction to focus or unfocus layer shell surfaces (e.g. requiring
|
||||
a click even if focus follows the mouse normally, or providing a
|
||||
keybinding to switch focus between layers).
|
||||
|
||||
This setting is mainly intended for desktop shell components (e.g.
|
||||
panels) that allow keyboard interaction. Using this option can allow
|
||||
implementing a desktop shell that can be fully usable without the
|
||||
mouse.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<request name="set_keyboard_interactivity">
|
||||
<description summary="requests keyboard events">
|
||||
Set how keyboard events are delivered to this surface. By default,
|
||||
layer shell surfaces do not receive keyboard events; this request can
|
||||
be used to change this.
|
||||
|
||||
This setting is inherited by child surfaces set by the get_popup
|
||||
request.
|
||||
|
||||
Layer surfaces receive pointer, touch, and tablet events normally. If
|
||||
you do not want to receive them, set the input region on your surface
|
||||
to an empty region.
|
||||
|
||||
Keyboard interactivity is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
|
||||
</request>
|
||||
|
||||
<request name="get_popup">
|
||||
<description summary="assign this layer_surface as an xdg_popup parent">
|
||||
This assigns an xdg_popup's parent to this layer_surface. This popup
|
||||
should have been created via xdg_surface::get_popup with the parent set
|
||||
to NULL, and this request must be invoked before committing the popup's
|
||||
initial state.
|
||||
|
||||
See the documentation of xdg_popup for more details about what an
|
||||
xdg_popup is and how it is used.
|
||||
</description>
|
||||
<arg name="popup" type="object" interface="xdg_popup"/>
|
||||
</request>
|
||||
|
||||
<request name="ack_configure">
|
||||
<description summary="ack a configure event">
|
||||
When a configure event is received, if a client commits the
|
||||
surface in response to the configure event, then the client
|
||||
must make an ack_configure request sometime before the commit
|
||||
request, passing along the serial of the configure event.
|
||||
|
||||
If the client receives multiple configure events before it
|
||||
can respond to one, it only has to ack the last configure event.
|
||||
|
||||
A client is not required to commit immediately after sending
|
||||
an ack_configure request - it may even ack_configure several times
|
||||
before its next surface commit.
|
||||
|
||||
A client may send multiple ack_configure requests before committing, but
|
||||
only the last request sent before a commit indicates which configure
|
||||
event the client really is responding to.
|
||||
</description>
|
||||
<arg name="serial" type="uint" summary="the serial from the configure event"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the layer_surface">
|
||||
This request destroys the layer surface.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="configure">
|
||||
<description summary="suggest a surface change">
|
||||
The configure event asks the client to resize its surface.
|
||||
|
||||
Clients should arrange their surface for the new states, and then send
|
||||
an ack_configure request with the serial sent in this configure event at
|
||||
some point before committing the new surface.
|
||||
|
||||
The client is free to dismiss all but the last configure event it
|
||||
received.
|
||||
|
||||
The width and height arguments specify the size of the window in
|
||||
surface-local coordinates.
|
||||
|
||||
The size is a hint, in the sense that the client is free to ignore it if
|
||||
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
|
||||
resize in steps of NxM pixels). If the client picks a smaller size and
|
||||
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
|
||||
surface will be centered on this axis.
|
||||
|
||||
If the width or height arguments are zero, it means the client should
|
||||
decide its own window dimension.
|
||||
</description>
|
||||
<arg name="serial" type="uint"/>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</event>
|
||||
|
||||
<event name="closed">
|
||||
<description summary="surface should be closed">
|
||||
The closed event is sent by the compositor when the surface will no
|
||||
longer be shown. The output may have been destroyed or the user may
|
||||
have asked for it to be removed. Further changes to the surface will be
|
||||
ignored. The client should destroy the resource after receiving this
|
||||
event, and create a new surface if they so choose.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
|
||||
<entry name="invalid_size" value="1" summary="size is invalid"/>
|
||||
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
|
||||
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
|
||||
<entry name="invalid_exclusive_edge" value="4" summary="exclusive edge is invalid given the surface anchors"/>
|
||||
</enum>
|
||||
|
||||
<enum name="anchor" bitfield="true">
|
||||
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
|
||||
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
|
||||
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
|
||||
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<request name="set_layer" since="2">
|
||||
<description summary="change the layer of the surface">
|
||||
Change the layer that the surface is rendered on.
|
||||
|
||||
Layer is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
|
||||
</request>
|
||||
|
||||
<!-- Version 5 additions -->
|
||||
|
||||
<request name="set_exclusive_edge" since="5">
|
||||
<description summary="set the edge the exclusive zone will be applied to">
|
||||
Requests an edge for the exclusive zone to apply. The exclusive
|
||||
edge will be automatically deduced from anchor points when possible,
|
||||
but when the surface is anchored to a corner, it will be necessary
|
||||
to set it explicitly to disambiguate, as it is not possible to deduce
|
||||
which one of the two corner edges should be used.
|
||||
|
||||
The edge must be one the surface is anchored to, otherwise the
|
||||
invalid_exclusive_edge protocol error will be raised.
|
||||
</description>
|
||||
<arg name="edge" type="uint" enum="anchor"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
@@ -1,11 +0,0 @@
|
||||
uniform sampler2D source;
|
||||
uniform vec2 size;
|
||||
uniform vec4 crop;
|
||||
uniform float alpha;
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec2 p = vec2(gl_FragCoord.x, size.y - gl_FragCoord.y) / size;
|
||||
vec4 c = texture(source, crop.xy + p * crop.zw);
|
||||
outColor = vec4(c.rgb, c.a * alpha);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
void main() {
|
||||
vec2 corner = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
||||
gl_Position = vec4(corner * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
// Bow-tie wipe transition adapted from gl-transitions.com
|
||||
// Author: huynx (MIT)
|
||||
// Original: two triangles grow from the top and bottom edges into a bow tie
|
||||
// that reveals the incoming image.
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
float check(vec2 p1, vec2 p2, vec2 p3) {
|
||||
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
|
||||
}
|
||||
|
||||
bool pointInTriangle(vec2 pt, vec2 p1, vec2 p2, vec2 p3) {
|
||||
bool b1 = check(pt, p1, p2) < 0.0;
|
||||
bool b2 = check(pt, p2, p3) < 0.0;
|
||||
bool b3 = check(pt, p3, p1) < 0.0;
|
||||
return (b1 == b2) && (b2 == b3);
|
||||
}
|
||||
|
||||
bool in_top_triangle(vec2 p) {
|
||||
vec2 vertex1 = vec2(0.5, progress);
|
||||
vec2 vertex2 = vec2(0.5 - progress, 0.0);
|
||||
vec2 vertex3 = vec2(0.5 + progress, 0.0);
|
||||
return pointInTriangle(p, vertex1, vertex2, vertex3);
|
||||
}
|
||||
|
||||
bool in_bottom_triangle(vec2 p) {
|
||||
vec2 vertex1 = vec2(0.5, 1.0 - progress);
|
||||
vec2 vertex2 = vec2(0.5 - progress, 1.0);
|
||||
vec2 vertex3 = vec2(0.5 + progress, 1.0);
|
||||
return pointInTriangle(p, vertex1, vertex2, vertex3);
|
||||
}
|
||||
|
||||
float blur_edge(vec2 bot1, vec2 bot2, vec2 top, vec2 testPt) {
|
||||
vec2 lineDir = bot1 - top;
|
||||
vec2 perpDir = vec2(lineDir.y, -lineDir.x);
|
||||
vec2 dirToPt1 = bot1 - testPt;
|
||||
float dist1 = abs(dot(normalize(perpDir), dirToPt1));
|
||||
|
||||
lineDir = bot2 - top;
|
||||
perpDir = vec2(lineDir.y, -lineDir.x);
|
||||
dirToPt1 = bot2 - testPt;
|
||||
float min_dist = min(abs(dot(normalize(perpDir), dirToPt1)), dist1);
|
||||
|
||||
if (min_dist < 0.005)
|
||||
return min_dist / 0.005;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
if (in_top_triangle(uv)) {
|
||||
if (progress < 0.1)
|
||||
return getFromColor(uv);
|
||||
|
||||
if (uv.y < 0.5) {
|
||||
vec2 vertex1 = vec2(0.5, progress);
|
||||
vec2 vertex2 = vec2(0.5 - progress, 0.0);
|
||||
vec2 vertex3 = vec2(0.5 + progress, 0.0);
|
||||
return mix(
|
||||
getFromColor(uv),
|
||||
getToColor(uv),
|
||||
blur_edge(vertex2, vertex3, vertex1, uv));
|
||||
}
|
||||
if (progress > 0.0)
|
||||
return getToColor(uv);
|
||||
return getFromColor(uv);
|
||||
}
|
||||
|
||||
if (in_bottom_triangle(uv)) {
|
||||
if (uv.y >= 0.5) {
|
||||
vec2 vertex1 = vec2(0.5, 1.0 - progress);
|
||||
vec2 vertex2 = vec2(0.5 - progress, 1.0);
|
||||
vec2 vertex3 = vec2(0.5 + progress, 1.0);
|
||||
return mix(
|
||||
getFromColor(uv),
|
||||
getToColor(uv),
|
||||
blur_edge(vertex2, vertex3, vertex1, uv));
|
||||
}
|
||||
return getFromColor(uv);
|
||||
}
|
||||
|
||||
return getFromColor(uv);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Doom-style melt wipe, from gl-transitions.com
|
||||
// Author: Zeh Fernando (MIT)
|
||||
// Description: the outgoing image melts down in uneven columns, like the Doom
|
||||
// screen wipe. Fully procedural (no displacement texture).
|
||||
//
|
||||
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag). Optional
|
||||
// gl-transitions params are fixed here: bars = 30, amplitude = 2.0,
|
||||
// noise = 0.1, frequency = 0.5, dripScale = 0.5.
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
const int bars = 30;
|
||||
const float amplitude = 2.0;
|
||||
const float noise = 0.1;
|
||||
const float frequency = 0.5;
|
||||
const float dripScale = 0.5;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
float rand(int num) {
|
||||
return fract(mod(float(num) * 67123.313, 12.0) * sin(float(num) * 10.3) *
|
||||
cos(float(num)));
|
||||
}
|
||||
|
||||
float wave(int num) {
|
||||
float fn = float(num) * frequency * 0.1 * float(bars);
|
||||
return cos(fn * 0.5) * cos(fn * 0.13) * sin((fn + 10.0) * 0.3) / 2.0 + 0.5;
|
||||
}
|
||||
|
||||
float drip(int num) {
|
||||
return sin(float(num) / float(bars - 1) * 3.141592) * dripScale;
|
||||
}
|
||||
|
||||
float pos(int num) {
|
||||
return (noise == 0.0 ? wave(num)
|
||||
: mix(wave(num), rand(num), noise)) +
|
||||
(dripScale == 0.0 ? 0.0 : drip(num));
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
int bar = int(uv.x * float(bars));
|
||||
float scale = 1.0 + pos(bar) * amplitude;
|
||||
float phase = progress * scale;
|
||||
float posY = uv.y;
|
||||
|
||||
if (phase + posY < 1.0) {
|
||||
return getFromColor(vec2(uv.x, uv.y + phase));
|
||||
}
|
||||
return getToColor(uv);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Fade (crossfade) transition, from gl-transitions.com.
|
||||
//
|
||||
// Ported to ZShell's own GLSL ABI (same contract as wallpaper_trans_bowtie.frag):
|
||||
// version/precision header is prepended by WallpaperService::shaderSource, the
|
||||
// fullscreen triangle comes from shaders/wallpaper.vert, and getFromColor /
|
||||
// getToColor sample with the crop + per-layer flip convention.
|
||||
//
|
||||
// uniforms: samplers `from`/`to`, vec2 size, vec4 fromCrop, vec4 toCrop,
|
||||
// float fromFlip, float toFlip, float progress (0..1).
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
uniform float progress;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
return mix(getFromColor(uv), getToColor(uv), progress);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// Hexagonalize crossfade, from gl-transitions.com
|
||||
// Author: Fernando Kuteken (MIT)
|
||||
// Description: both images turn into hexagons, crossfade, then sharpen again.
|
||||
// Hexagonal math from http://www.redblobgames.com/grids/hexagons/
|
||||
//
|
||||
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag). The
|
||||
// gl-transitions `ratio` contextual value is derived from `size`. Optional
|
||||
// params are fixed here: steps = 50, horizontalHexagons = 20.
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
const int steps = 50;
|
||||
const float horizontalHexagons = 20.0;
|
||||
float ratio;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
struct Hexagon {
|
||||
float q;
|
||||
float r;
|
||||
float s;
|
||||
};
|
||||
|
||||
Hexagon createHexagon(float q, float r) {
|
||||
Hexagon hex;
|
||||
hex.q = q;
|
||||
hex.r = r;
|
||||
hex.s = -q - r;
|
||||
return hex;
|
||||
}
|
||||
|
||||
Hexagon roundHexagon(Hexagon hex) {
|
||||
float q = floor(hex.q + 0.5);
|
||||
float r = floor(hex.r + 0.5);
|
||||
float s = floor(hex.s + 0.5);
|
||||
|
||||
float deltaQ = abs(q - hex.q);
|
||||
float deltaR = abs(r - hex.r);
|
||||
float deltaS = abs(s - hex.s);
|
||||
|
||||
if (deltaQ > deltaR && deltaQ > deltaS)
|
||||
q = -r - s;
|
||||
else if (deltaR > deltaS)
|
||||
r = -q - s;
|
||||
else
|
||||
s = -q - r;
|
||||
|
||||
return createHexagon(q, r);
|
||||
}
|
||||
|
||||
Hexagon hexagonFromPoint(vec2 point, float size) {
|
||||
point.y /= ratio;
|
||||
point = (point - 0.5) / size;
|
||||
|
||||
float q = (sqrt(3.0) / 3.0) * point.x + (-1.0 / 3.0) * point.y;
|
||||
float r = 0.0 * point.x + 2.0 / 3.0 * point.y;
|
||||
|
||||
Hexagon hex = createHexagon(q, r);
|
||||
return roundHexagon(hex);
|
||||
}
|
||||
|
||||
vec2 pointFromHexagon(Hexagon hex, float size) {
|
||||
float x = (sqrt(3.0) * hex.q + (sqrt(3.0) / 2.0) * hex.r) * size + 0.5;
|
||||
float y = (0.0 * hex.q + (3.0 / 2.0) * hex.r) * size + 0.5;
|
||||
return vec2(x, y * ratio);
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
float dist = 2.0 * min(progress, 1.0 - progress);
|
||||
dist = steps > 0 ? ceil(dist * float(steps)) / float(steps) : dist;
|
||||
|
||||
float size = (sqrt(3.0) / 3.0) * dist / horizontalHexagons;
|
||||
|
||||
vec2 point = dist > 0.0 ? pointFromHexagon(hexagonFromPoint(uv, size), size)
|
||||
: uv;
|
||||
|
||||
return mix(getFromColor(point), getToColor(point), progress);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
ratio = size.x / size.y;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Linear blur crossfade, from gl-transitions.com
|
||||
// Author: gre (MIT)
|
||||
// Description: a crossfade that passes through a directional blur.
|
||||
//
|
||||
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag): the version
|
||||
// header is prepended by WallpaperService::shaderSource, the fullscreen
|
||||
// triangle comes from wallpaper.vert, and getFromColor/getToColor apply the
|
||||
// crop + flip convention. `uv` is bottom-left origin per gl-transitions.
|
||||
//
|
||||
// Optional gl-transitions params are fixed here: intensity = 0.1.
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
const float intensity = 0.1;
|
||||
const int passes = 6;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
vec4 c1 = vec4(0.0);
|
||||
vec4 c2 = vec4(0.0);
|
||||
|
||||
float disp = intensity * (0.5 - distance(0.5, progress));
|
||||
for (int xi = 0; xi < passes; xi++) {
|
||||
float x = float(xi) / float(passes) - 0.5;
|
||||
for (int yi = 0; yi < passes; yi++) {
|
||||
float y = float(yi) / float(passes) - 0.5;
|
||||
vec2 v = vec2(x, y);
|
||||
c1 += getFromColor(uv + disp * v);
|
||||
c2 += getToColor(uv + disp * v);
|
||||
}
|
||||
}
|
||||
c1 /= float(passes * passes);
|
||||
c2 /= float(passes * passes);
|
||||
return mix(c1, c2, progress);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Split slide-in (horizontal), from gl-transitions.com
|
||||
// Author: OllyOllyOlly (MIT)
|
||||
// Description: the incoming image slides in as two halves moving horizontally
|
||||
// in opposite directions.
|
||||
//
|
||||
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||
// Optional gl-transitions params are fixed here: reverse = false.
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
const vec2 boundMin = vec2(0.0, 0.0);
|
||||
const vec2 boundMax = vec2(1.0, 1.0);
|
||||
|
||||
bool inBounds(vec2 p) {
|
||||
return all(lessThan(boundMin, p)) && all(lessThan(p, boundMax));
|
||||
}
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
float modifier = 1.0;
|
||||
vec2 toP = (uv.y > 0.5)
|
||||
? vec2((uv.x - (progress * modifier)) + modifier, uv.y)
|
||||
: vec2((uv.x + (progress * modifier)) - modifier, uv.y);
|
||||
|
||||
return inBounds(toP) ? getToColor(toP) : getFromColor(uv);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// Tiles wave reveal, from gl-transitions.com
|
||||
// Author: numb3r23 (MIT)
|
||||
// Description: the incoming image is revealed tile by tile in a diagonal wave.
|
||||
//
|
||||
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||
// Optional gl-transitions params are fixed here: tileCount = (8, 8),
|
||||
// flipX = true, flipY = false.
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
const ivec2 tileCount = ivec2(8, 8);
|
||||
const bool flipX = true;
|
||||
const bool flipY = false;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
vec2 tileSize = 1.0 / vec2(tileCount);
|
||||
vec2 posInTile = fract(uv * vec2(tileCount));
|
||||
vec2 tileNum = floor(uv * vec2(tileCount));
|
||||
float countTiles = float(tileCount.x * tileCount.y);
|
||||
|
||||
// Diagonal wave from bottom-left to top-right.
|
||||
float offset = (tileNum.y + tileNum.x * float(tileCount.y)) / countTiles;
|
||||
float timeOffset = clamp((progress - offset) * countTiles, 0.0, 0.5);
|
||||
float sinTime = 1.0 - abs(cos(fract(timeOffset) * 3.1415926));
|
||||
|
||||
vec2 texC = posInTile;
|
||||
|
||||
if (sinTime <= 0.5) {
|
||||
if (flipX) {
|
||||
if (texC.x < sinTime || texC.x > 1.0 - sinTime)
|
||||
return getFromColor(uv);
|
||||
texC.x = texC.x < 0.5
|
||||
? (texC.x - sinTime) * 0.5 / (0.5 - sinTime)
|
||||
: (texC.x - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
|
||||
}
|
||||
if (flipY) {
|
||||
if (texC.y < sinTime || texC.y > 1.0 - sinTime)
|
||||
return getFromColor(uv);
|
||||
texC.y = texC.y < 0.5
|
||||
? (texC.y - sinTime) * 0.5 / (0.5 - sinTime)
|
||||
: (texC.y - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
|
||||
}
|
||||
vec2 globalUV = tileNum * tileSize + texC * tileSize;
|
||||
return getFromColor(globalUV);
|
||||
} else {
|
||||
if (flipX) {
|
||||
if (texC.x > sinTime || texC.x < 1.0 - sinTime)
|
||||
return getToColor(uv);
|
||||
texC.x = texC.x < 0.5
|
||||
? (texC.x - sinTime) * 0.5 / (0.5 - sinTime)
|
||||
: (texC.x - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
|
||||
texC.x = 1.0 - texC.x;
|
||||
}
|
||||
if (flipY) {
|
||||
if (texC.y > sinTime || texC.y < 1.0 - sinTime)
|
||||
return getToColor(uv);
|
||||
texC.y = texC.y < 0.5
|
||||
? (texC.y - sinTime) * 0.5 / (0.5 - sinTime)
|
||||
: (texC.y - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
|
||||
texC.y = 1.0 - texC.y;
|
||||
}
|
||||
vec2 globalUV = tileNum * tileSize + texC * tileSize;
|
||||
return getToColor(globalUV);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Window slices wipe, from gl-transitions.com
|
||||
// Author: gre (MIT)
|
||||
// Description: the incoming image appears through vertical slices that widen.
|
||||
//
|
||||
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||
// Optional gl-transitions params are fixed here: count = 10.0,
|
||||
// smoothness = 0.5.
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
const float count = 10.0;
|
||||
const float smoothness = 0.5;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
float pr = smoothstep(-smoothness, 0.0, uv.x - progress * (1.0 + smoothness));
|
||||
float s = step(pr, fract(count * uv.x));
|
||||
return mix(getFromColor(uv), getToColor(uv), s);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Hard-edged wipe from top to bottom, from gl-transitions.com
|
||||
// Author: Jake Nelson (MIT)
|
||||
//
|
||||
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
return mix(getFromColor(uv), getToColor(uv), step(1.0 - uv.y, progress));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Hard-edged wipe from left to right, from gl-transitions.com
|
||||
// Author: Jake Nelson (MIT)
|
||||
//
|
||||
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||
|
||||
uniform sampler2D from;
|
||||
uniform sampler2D to;
|
||||
uniform vec2 size;
|
||||
uniform vec4 fromCrop;
|
||||
uniform vec4 toCrop;
|
||||
uniform float progress;
|
||||
uniform float fromFlip;
|
||||
uniform float toFlip;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
vec4 getFromColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||
}
|
||||
|
||||
vec4 getToColor(vec2 uv) {
|
||||
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||
}
|
||||
|
||||
vec4 transition(vec2 uv) {
|
||||
return mix(getFromColor(uv), getToColor(uv), step(0.0 + uv.x, progress));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / size;
|
||||
outColor = transition(uv);
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
uniform sampler2D luma;
|
||||
uniform sampler2D chroma;
|
||||
uniform vec2 size;
|
||||
uniform vec2 srcSize;
|
||||
uniform vec4 crop;
|
||||
uniform float alpha;
|
||||
uniform int matrix;
|
||||
uniform int range;
|
||||
uniform vec2 chromaOff;
|
||||
out vec4 outColor;
|
||||
|
||||
float noise(vec2 p) {
|
||||
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
const float PI = 3.1415926535893;
|
||||
|
||||
float sinc(float x) {
|
||||
x *= PI;
|
||||
return abs(x) < 1e-6 ? 1.0 : sin(x) / x;
|
||||
}
|
||||
|
||||
float lanczos(float x) {
|
||||
if (abs(x) >= 3.0)
|
||||
return 0.0;
|
||||
return sinc(x) * sinc(x / 3.0);
|
||||
}
|
||||
|
||||
float hermite(float x) {
|
||||
float a = abs(x);
|
||||
if (a >= 1.0)
|
||||
return 0.0;
|
||||
return 1.0 - 3.0 * a * a + 2.0 * a * a * a;
|
||||
}
|
||||
|
||||
vec4 resample(sampler2D tex, vec2 plane, vec2 dest, vec2 uv) {
|
||||
vec2 pt = 1.0 / plane;
|
||||
float wx[6];
|
||||
float wy[6];
|
||||
vec2 base;
|
||||
|
||||
int upx = dest.x >= plane.x ? 1 : 0;
|
||||
int tx = upx == 1 ? 6 : 2;
|
||||
float fsx = max(1.0, plane.x / dest.x);
|
||||
float f = fract(uv.x * plane.x - 0.5);
|
||||
base.x = uv.x - f * pt.x - pt.x * (float(tx) * 0.5 - 1.0);
|
||||
float sx = 0.0;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (i >= tx) { wx[i] = 0.0; continue; }
|
||||
float x = f - (float(i) - float(tx) * 0.5 + 1.0);
|
||||
wx[i] = upx == 1 ? lanczos(x / fsx) : hermite(x / fsx);
|
||||
sx += wx[i];
|
||||
}
|
||||
if (sx != 0.0)
|
||||
for (int i = 0; i < 6; ++i) wx[i] /= sx;
|
||||
|
||||
int upy = dest.y >= plane.y ? 1 : 0;
|
||||
int ty = upy == 1 ? 6 : 2;
|
||||
float fsy = max(1.0, plane.y / dest.y);
|
||||
f = fract(uv.y * plane.y - 0.5);
|
||||
base.y = uv.y - f * pt.y - pt.y * (float(ty) * 0.5 - 1.0);
|
||||
float sy = 0.0;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (i >= ty) { wy[i] = 0.0; continue; }
|
||||
float y = f - (float(i) - float(ty) * 0.5 + 1.0);
|
||||
wy[i] = upy == 1 ? lanczos(y / fsy) : hermite(y / fsy);
|
||||
sy += wy[i];
|
||||
}
|
||||
if (sy != 0.0)
|
||||
for (int i = 0; i < 6; ++i) wy[i] /= sy;
|
||||
|
||||
vec4 col = vec4(0.0);
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (wx[i] == 0.0)
|
||||
continue;
|
||||
for (int j = 0; j < 6; ++j) {
|
||||
if (wy[j] == 0.0)
|
||||
continue;
|
||||
vec2 c = base + vec2(float(i) * pt.x, float(j) * pt.y);
|
||||
col += (wx[i] * wy[j]) * texture(tex, c);
|
||||
}
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = vec2(gl_FragCoord.x, size.y - gl_FragCoord.y) / size;
|
||||
vec2 uv = crop.xy + p * crop.zw;
|
||||
float Yv = resample(luma, srcSize, size, uv).r;
|
||||
vec2 ch = resample(chroma, srcSize * 0.5, size, uv + chromaOff).rg;
|
||||
vec3 yuv;
|
||||
if (range == 0) {
|
||||
yuv = vec3((Yv - 16.0 / 255.0) / (219.0 / 255.0),
|
||||
(ch.r - 128.0 / 255.0) / (224.0 / 255.0),
|
||||
(ch.g - 128.0 / 255.0) / (224.0 / 255.0));
|
||||
} else {
|
||||
yuv = vec3(Yv, ch.r - 0.5, ch.g - 0.5);
|
||||
}
|
||||
mat3 m = matrix == 1
|
||||
? mat3(1.0, 1.0, 1.0, 0.0, -0.187324, 1.855600, 1.574800, -0.468124, 0.0)
|
||||
: mat3(1.0, 1.0, 1.0, 0.0, -0.344136, 1.772000, 1.402000, -0.714136, 0.0);
|
||||
vec3 rgb = m * yuv;
|
||||
rgb += (noise(gl_FragCoord.xy) - 0.5) / 255.0;
|
||||
outColor = vec4(clamp(rgb, 0.0, 1.0), alpha);
|
||||
}
|
||||
@@ -1,549 +0,0 @@
|
||||
#include "videoplayer.hpp"
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
|
||||
#define EGL_EGLEXT_PROTOTYPES
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#include <cuda.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
|
||||
#include <QtLogging>
|
||||
|
||||
extern "C" CUresult cuGraphicsGLRegisterImage(
|
||||
CUgraphicsResource* resource,
|
||||
GLuint image,
|
||||
GLenum target,
|
||||
unsigned int flags);
|
||||
|
||||
Q_LOGGING_CATEGORY(videoLog, "ZShell.Internal.wallpaper")
|
||||
|
||||
namespace {
|
||||
|
||||
enum AVPixelFormat chooseFormat(
|
||||
AVCodecContext* ctx, const AVPixelFormat* pix_fmts) {
|
||||
(void)ctx;
|
||||
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p)
|
||||
if (*p == AV_PIX_FMT_CUDA) return AV_PIX_FMT_CUDA;
|
||||
return pix_fmts[0];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
VideoPlayer::VideoPlayer(EGLDisplay display) : m_eglDisplay(display) {}
|
||||
|
||||
VideoPlayer::~VideoPlayer() {
|
||||
stop();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
if (m_pending) {
|
||||
av_frame_unref(m_pending);
|
||||
av_frame_free(&m_pending);
|
||||
}
|
||||
if (m_displayed) {
|
||||
av_frame_unref(m_displayed);
|
||||
av_frame_free(&m_displayed);
|
||||
}
|
||||
|
||||
if (m_cuCtx) {
|
||||
CUcontext prev = nullptr;
|
||||
cuCtxPushCurrent(m_cuCtx);
|
||||
if (m_resY) {
|
||||
cuGraphicsUnregisterResource(m_resY);
|
||||
m_resY = nullptr;
|
||||
}
|
||||
if (m_resUV) {
|
||||
cuGraphicsUnregisterResource(m_resUV);
|
||||
m_resUV = nullptr;
|
||||
}
|
||||
if (m_texY) {
|
||||
glDeleteTextures(1, &m_texY);
|
||||
m_texY = 0;
|
||||
}
|
||||
if (m_texUV) {
|
||||
glDeleteTextures(1, &m_texUV);
|
||||
m_texUV = 0;
|
||||
}
|
||||
cuCtxPopCurrent(&prev);
|
||||
cuCtxDestroy(m_cuCtx);
|
||||
m_cuCtx = nullptr;
|
||||
}
|
||||
m_texSize = QSize();
|
||||
}
|
||||
|
||||
void VideoPlayer::setReadyCallback(std::function<void()> callback) {
|
||||
m_readyCb = std::move(callback);
|
||||
}
|
||||
|
||||
void VideoPlayer::setFrameCallback(std::function<void()> callback) {
|
||||
m_frameCb = std::move(callback);
|
||||
}
|
||||
|
||||
bool VideoPlayer::openDecoder(const QString& path) {
|
||||
if (avformat_open_input(
|
||||
&m_fmt, path.toUtf8().constData(), nullptr, nullptr) < 0) {
|
||||
qCWarning(
|
||||
videoLog,
|
||||
"VideoPlayer: could not open %s",
|
||||
path.toUtf8().constData());
|
||||
return false;
|
||||
}
|
||||
if (avformat_find_stream_info(m_fmt, nullptr) < 0) {
|
||||
qCWarning(videoLog, "VideoPlayer: could not read stream info");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_streamIndex = -1;
|
||||
for (unsigned i = 0; i < m_fmt->nb_streams; ++i)
|
||||
if (m_fmt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
m_streamIndex = int(i);
|
||||
break;
|
||||
}
|
||||
if (m_streamIndex < 0) {
|
||||
qCWarning(videoLog, "VideoPlayer: no video stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
AVCodecParameters* par = m_fmt->streams[m_streamIndex]->codecpar;
|
||||
const AVCodec* codec = avcodec_find_decoder(par->codec_id);
|
||||
const AVCodec* hwcodec = nullptr;
|
||||
void* it = nullptr;
|
||||
while (const AVCodec* c = av_codec_iterate(&it)) {
|
||||
if (c->id != par->codec_id || !av_codec_is_decoder(c)) continue;
|
||||
for (int i = 0;
|
||||
const AVCodecHWConfig* cfg = avcodec_get_hw_config(c, i);
|
||||
++i)
|
||||
if (cfg->device_type == AV_HWDEVICE_TYPE_CUDA &&
|
||||
(cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
||||
hwcodec = c;
|
||||
break;
|
||||
}
|
||||
if (hwcodec) break;
|
||||
}
|
||||
if (hwcodec) codec = hwcodec;
|
||||
if (!codec) {
|
||||
qCWarning(
|
||||
videoLog,
|
||||
"VideoPlayer: no decoder for codec id %d",
|
||||
int(par->codec_id));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_codec = avcodec_alloc_context3(codec);
|
||||
if (!m_codec) return false;
|
||||
if (avcodec_parameters_to_context(m_codec, par) < 0) {
|
||||
qCWarning(videoLog, "VideoPlayer: could not copy codec params");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hwDeviceCtx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
|
||||
if (!m_hwDeviceCtx) {
|
||||
qCWarning(
|
||||
videoLog, "VideoPlayer: could not allocate cuda hwdevice context");
|
||||
return false;
|
||||
}
|
||||
AVCUDADeviceContext* cudaHw = reinterpret_cast<AVCUDADeviceContext*>(
|
||||
reinterpret_cast<AVHWDeviceContext*>(m_hwDeviceCtx->data)->hwctx);
|
||||
if (m_cuCtx) cudaHw->cuda_ctx = m_cuCtx;
|
||||
if (av_hwdevice_ctx_init(m_hwDeviceCtx) < 0) {
|
||||
qCWarning(
|
||||
videoLog, "VideoPlayer: could not create cuda hwdevice context");
|
||||
av_buffer_unref(&m_hwDeviceCtx);
|
||||
return false;
|
||||
}
|
||||
m_codec->hw_device_ctx = av_buffer_ref(m_hwDeviceCtx);
|
||||
m_codec->get_format = chooseFormat;
|
||||
|
||||
if (avcodec_open2(m_codec, codec, nullptr) < 0) {
|
||||
qCWarning(videoLog, "VideoPlayer: could not open codec");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoPlayer::closeDecoder() {
|
||||
if (m_codec) {
|
||||
avcodec_free_context(&m_codec);
|
||||
m_codec = nullptr;
|
||||
}
|
||||
if (m_hwDeviceCtx) {
|
||||
av_buffer_unref(&m_hwDeviceCtx);
|
||||
m_hwDeviceCtx = nullptr;
|
||||
}
|
||||
if (m_fmt) {
|
||||
avformat_close_input(&m_fmt);
|
||||
m_fmt = nullptr;
|
||||
}
|
||||
m_streamIndex = -1;
|
||||
}
|
||||
|
||||
void VideoPlayer::decodeLoop(const QString& path, uint32_t generation) {
|
||||
if (!m_threadRunning.load(std::memory_order_acquire) ||
|
||||
m_generation.load(std::memory_order_acquire) != generation)
|
||||
return;
|
||||
|
||||
if (!openDecoder(path)) {
|
||||
m_threadRunning.store(false);
|
||||
return;
|
||||
}
|
||||
|
||||
AVRational fr = m_fmt->streams[m_streamIndex]->avg_frame_rate;
|
||||
if (fr.num <= 0 || fr.den <= 0) fr = m_codec->framerate;
|
||||
if (fr.num <= 0 || fr.den <= 0) fr = AVRational{30, 1};
|
||||
const int64_t intervalUs =
|
||||
av_rescale_q(1, AVRational{fr.den, fr.num}, AVRational{1, 1000000});
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
m_threadRunning.store(false);
|
||||
return;
|
||||
}
|
||||
|
||||
AVPacket pkt{};
|
||||
|
||||
while (m_threadRunning.load(std::memory_order_acquire) &&
|
||||
m_generation.load(std::memory_order_acquire) == generation) {
|
||||
int ret = av_read_frame(m_fmt, &pkt);
|
||||
if (ret == AVERROR_EOF) {
|
||||
if (avformat_seek_file(
|
||||
m_fmt, m_streamIndex, INT64_MIN, 0, INT64_MAX, 0) < 0) {
|
||||
qCWarning(videoLog, "VideoPlayer: loop seek failed; stopping");
|
||||
break;
|
||||
}
|
||||
avcodec_flush_buffers(m_codec);
|
||||
continue;
|
||||
}
|
||||
if (ret < 0) {
|
||||
qCWarning(videoLog, "VideoPlayer: read error; stopping");
|
||||
break;
|
||||
}
|
||||
if (pkt.stream_index != m_streamIndex) {
|
||||
av_packet_unref(&pkt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (avcodec_send_packet(m_codec, &pkt) < 0) {
|
||||
av_packet_unref(&pkt);
|
||||
continue;
|
||||
}
|
||||
av_packet_unref(&pkt);
|
||||
|
||||
while (m_threadRunning.load(std::memory_order_acquire) &&
|
||||
m_generation.load(std::memory_order_acquire) == generation &&
|
||||
avcodec_receive_frame(m_codec, frame) == 0) {
|
||||
bool wasEmpty = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
if (!m_pending) m_pending = av_frame_alloc();
|
||||
av_frame_unref(m_pending);
|
||||
if (av_frame_ref(m_pending, frame) >= 0) {
|
||||
wasEmpty = !m_haveFrame;
|
||||
m_haveFrame = true;
|
||||
}
|
||||
}
|
||||
if (wasEmpty && m_readyCb) m_readyCb();
|
||||
if (m_frameCb) m_frameCb();
|
||||
av_frame_unref(frame);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(intervalUs));
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
closeDecoder();
|
||||
m_threadRunning.store(false);
|
||||
}
|
||||
|
||||
bool VideoPlayer::start(const QString& path) {
|
||||
const uint32_t generation =
|
||||
m_generation.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
m_threadRunning.store(true);
|
||||
|
||||
std::thread previous = std::move(m_thread);
|
||||
m_thread = std::thread(
|
||||
[this, path, generation, previous = std::move(previous)]() mutable {
|
||||
if (previous.joinable()) previous.join();
|
||||
closeDecoder();
|
||||
|
||||
if (!ensureCudaContext()) {
|
||||
m_threadRunning.store(false);
|
||||
return;
|
||||
}
|
||||
|
||||
decodeLoop(path, generation);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoPlayer::stop() {
|
||||
m_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||
m_threadRunning.store(false);
|
||||
if (m_thread.joinable()) m_thread.join();
|
||||
|
||||
closeDecoder();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
if (m_pending) {
|
||||
av_frame_unref(m_pending);
|
||||
m_pending = nullptr;
|
||||
}
|
||||
if (m_displayed) {
|
||||
av_frame_unref(m_displayed);
|
||||
m_displayed = nullptr;
|
||||
}
|
||||
m_haveFrame = false;
|
||||
m_size = QSize();
|
||||
}
|
||||
|
||||
QSize VideoPlayer::frameSize() const {
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void VideoPlayer::deleteTexture() {
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
CUcontext prev = nullptr;
|
||||
if (m_cuCtx) cuCtxPushCurrent(m_cuCtx);
|
||||
if (m_resY) {
|
||||
cuGraphicsUnregisterResource(m_resY);
|
||||
m_resY = nullptr;
|
||||
}
|
||||
if (m_resUV) {
|
||||
cuGraphicsUnregisterResource(m_resUV);
|
||||
m_resUV = nullptr;
|
||||
}
|
||||
if (m_texY) {
|
||||
glDeleteTextures(1, &m_texY);
|
||||
m_texY = 0;
|
||||
}
|
||||
if (m_texUV) {
|
||||
glDeleteTextures(1, &m_texUV);
|
||||
m_texUV = 0;
|
||||
}
|
||||
if (m_cuCtx) cuCtxPopCurrent(&prev);
|
||||
m_texSize = QSize();
|
||||
}
|
||||
|
||||
bool VideoPlayer::ensureCudaContext() {
|
||||
if (m_cuCtx) return true;
|
||||
|
||||
if (cuInit(0) != CUDA_SUCCESS) {
|
||||
qCWarning(videoLog, "VideoPlayer: cuInit failed");
|
||||
return false;
|
||||
}
|
||||
CUdevice dev = 0;
|
||||
if (cuDeviceGet(&dev, 0) != CUDA_SUCCESS) {
|
||||
qCWarning(videoLog, "VideoPlayer: cuDeviceGet failed");
|
||||
return false;
|
||||
}
|
||||
if (cuCtxCreate_v4(&m_cuCtx, nullptr, 0, dev) != CUDA_SUCCESS) {
|
||||
m_cuCtx = nullptr;
|
||||
qCWarning(videoLog, "VideoPlayer: cuCtxCreate failed");
|
||||
return false;
|
||||
}
|
||||
CUcontext popped = nullptr;
|
||||
cuCtxPopCurrent(&popped);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VideoPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
|
||||
if (!ensureCudaContext()) return false;
|
||||
CUcontext prev = nullptr;
|
||||
cuCtxPushCurrent(m_cuCtx);
|
||||
|
||||
const QSize sz = QSize(int(w), int(h));
|
||||
if (m_texSize == sz && m_texComp == compBytes && m_texY != 0) {
|
||||
cuCtxPopCurrent(&prev);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_resY) cuGraphicsUnregisterResource(m_resY);
|
||||
if (m_resUV) cuGraphicsUnregisterResource(m_resUV);
|
||||
m_resY = m_resUV = nullptr;
|
||||
if (m_texY) glDeleteTextures(1, &m_texY);
|
||||
if (m_texUV) glDeleteTextures(1, &m_texUV);
|
||||
m_texY = m_texUV = 0;
|
||||
|
||||
const bool wide = compBytes == 2;
|
||||
auto makeTex =
|
||||
[&](GLuint& tex, GLint internal, GLenum fmt, unsigned tw, unsigned th) {
|
||||
glGenTextures(1, &tex);
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
internal,
|
||||
GLint(tw),
|
||||
GLint(th),
|
||||
0,
|
||||
fmt,
|
||||
wide ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
};
|
||||
makeTex(m_texY, wide ? GL_R16 : GL_R8, GL_RED, w, h);
|
||||
makeTex(m_texUV, wide ? GL_RG16 : GL_RG8, GL_RG, w / 2, h / 2);
|
||||
|
||||
CUresult r = cuGraphicsGLRegisterImage(
|
||||
&m_resY,
|
||||
m_texY,
|
||||
GL_TEXTURE_2D,
|
||||
CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD);
|
||||
if (r != CUDA_SUCCESS) {
|
||||
const char* es = nullptr;
|
||||
cuGetErrorString(r, &es);
|
||||
qCWarning(
|
||||
videoLog,
|
||||
"VideoPlayer: register luma tex failed (%s)",
|
||||
es ? es : "?");
|
||||
m_texSize = QSize();
|
||||
cuCtxPopCurrent(&prev);
|
||||
return false;
|
||||
}
|
||||
r = cuGraphicsGLRegisterImage(
|
||||
&m_resUV,
|
||||
m_texUV,
|
||||
GL_TEXTURE_2D,
|
||||
CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD);
|
||||
if (r != CUDA_SUCCESS) {
|
||||
qCWarning(videoLog, "VideoPlayer: register chroma tex failed");
|
||||
cuGraphicsUnregisterResource(m_resY);
|
||||
m_resY = nullptr;
|
||||
m_texSize = QSize();
|
||||
cuCtxPopCurrent(&prev);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_texSize = sz;
|
||||
m_texComp = compBytes;
|
||||
cuCtxPopCurrent(&prev);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VideoPlayer::importCurrentFrame() {
|
||||
AVFrame* frame = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
if (!m_pending || m_pending->format == AV_PIX_FMT_NONE) return false;
|
||||
frame = m_pending;
|
||||
m_pending = nullptr;
|
||||
}
|
||||
|
||||
if (frame->format != AV_PIX_FMT_CUDA || !frame->hw_frames_ctx) {
|
||||
static int logged = 0;
|
||||
if (logged++ < 3)
|
||||
qCWarning(
|
||||
videoLog,
|
||||
"VideoPlayer: frame not on GPU (fmt=%s hw_frames=%d)",
|
||||
av_get_pix_fmt_name(AVPixelFormat(frame->format)),
|
||||
frame->hw_frames_ctx != nullptr);
|
||||
av_frame_unref(frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
const AVHWFramesContext* hf =
|
||||
reinterpret_cast<const AVHWFramesContext*>(frame->hw_frames_ctx->data);
|
||||
const AVPixelFormat sw = hf->sw_format;
|
||||
const bool tenBit = sw == AV_PIX_FMT_P010LE || sw == AV_PIX_FMT_P010BE ||
|
||||
sw == AV_PIX_FMT_P012LE || sw == AV_PIX_FMT_P016LE;
|
||||
const int compBytes = tenBit ? 2 : 1;
|
||||
const size_t compSize = size_t(compBytes);
|
||||
|
||||
const unsigned w = unsigned(frame->width);
|
||||
const unsigned h = unsigned(frame->height);
|
||||
if (w == 0 || h == 0) {
|
||||
av_frame_unref(frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pitchY = size_t(frame->linesize[0]);
|
||||
size_t pitchUV = frame->linesize[1] > 0 ? size_t(frame->linesize[1])
|
||||
: pitchY;
|
||||
if (pitchY == 0) pitchY = size_t(w) * compSize;
|
||||
|
||||
switch (frame->colorspace) {
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
m_matrix = 0;
|
||||
break;
|
||||
case AVCOL_SPC_BT709:
|
||||
m_matrix = 1;
|
||||
break;
|
||||
default:
|
||||
m_matrix = h >= 720 ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
m_range = frame->color_range == AVCOL_RANGE_JPEG ? 1 : 0;
|
||||
m_size = QSize(int(w), int(h));
|
||||
|
||||
if (!ensureTextures(w, h, compBytes)) {
|
||||
av_frame_unref(frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
CUcontext prev = nullptr;
|
||||
cuCtxPushCurrent(m_cuCtx);
|
||||
CUgraphicsResource res[2] = {m_resY, m_resUV};
|
||||
CUresult r = cuGraphicsMapResources(2, res, nullptr);
|
||||
if (r != CUDA_SUCCESS) {
|
||||
cuCtxPopCurrent(&prev);
|
||||
av_frame_unref(frame);
|
||||
qCWarning(videoLog, "VideoPlayer: cuGraphicsMapResources failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
CUarray arrY = nullptr, arrUV = nullptr;
|
||||
cuGraphicsSubResourceGetMappedArray(&arrY, m_resY, 0, 0);
|
||||
cuGraphicsSubResourceGetMappedArray(&arrUV, m_resUV, 0, 0);
|
||||
|
||||
const size_t dstPitch = size_t(w) * compSize;
|
||||
CUDA_MEMCPY2D mc{};
|
||||
mc.srcMemoryType = CU_MEMORYTYPE_DEVICE;
|
||||
mc.dstMemoryType = CU_MEMORYTYPE_ARRAY;
|
||||
mc.srcDevice = CUdeviceptr(frame->data[0]);
|
||||
mc.srcPitch = pitchY;
|
||||
mc.dstArray = arrY;
|
||||
mc.dstPitch = dstPitch;
|
||||
mc.WidthInBytes = std::min(pitchY, dstPitch);
|
||||
mc.Height = h;
|
||||
r = cuMemcpy2D(&mc);
|
||||
if (r == CUDA_SUCCESS) {
|
||||
mc.srcDevice = CUdeviceptr(frame->data[1]);
|
||||
mc.srcPitch = pitchUV;
|
||||
mc.dstArray = arrUV;
|
||||
mc.dstPitch = dstPitch;
|
||||
mc.WidthInBytes = std::min(pitchUV, dstPitch);
|
||||
mc.Height = h / 2;
|
||||
r = cuMemcpy2D(&mc);
|
||||
}
|
||||
|
||||
cuGraphicsUnmapResources(2, res, nullptr);
|
||||
cuCtxPopCurrent(&prev);
|
||||
|
||||
if (r != CUDA_SUCCESS) {
|
||||
av_frame_unref(frame);
|
||||
qCWarning(videoLog, "VideoPlayer: cuMemcpy2D failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
if (m_displayed) av_frame_unref(m_displayed);
|
||||
m_displayed = frame;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <cuda.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/buffer.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/hwcontext.h>
|
||||
#include <libavutil/hwcontext_cuda.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
class VideoPlayer {
|
||||
public:
|
||||
explicit VideoPlayer(EGLDisplay display);
|
||||
~VideoPlayer();
|
||||
|
||||
void setReadyCallback(std::function<void()> callback);
|
||||
void setFrameCallback(std::function<void()> callback);
|
||||
|
||||
bool start(const QString& path);
|
||||
void stop();
|
||||
|
||||
|
||||
bool importCurrentFrame();
|
||||
|
||||
void deleteTexture();
|
||||
|
||||
[[nodiscard]] uint32_t textureId() const { return m_texY; }
|
||||
[[nodiscard]] uint32_t chromaTextureId() const { return m_texUV; }
|
||||
[[nodiscard]] int colorMatrix() const { return m_matrix; }
|
||||
[[nodiscard]] int colorRange() const { return m_range; }
|
||||
[[nodiscard]] QSize frameSize() const;
|
||||
|
||||
private:
|
||||
void decodeLoop(const QString& path, uint32_t generation);
|
||||
bool openDecoder(const QString& path);
|
||||
void closeDecoder();
|
||||
|
||||
bool ensureTextures(unsigned w, unsigned h, int compBytes);
|
||||
|
||||
bool ensureCudaContext();
|
||||
|
||||
EGLDisplay m_eglDisplay;
|
||||
std::function<void()> m_readyCb;
|
||||
std::function<void()> m_frameCb;
|
||||
|
||||
std::thread m_thread;
|
||||
std::atomic<bool> m_threadRunning{false};
|
||||
std::atomic<uint32_t> m_generation{0};
|
||||
|
||||
mutable std::mutex m_mtx;
|
||||
AVFrame* m_pending = nullptr;
|
||||
bool m_haveFrame = false;
|
||||
|
||||
AVFrame* m_displayed = nullptr;
|
||||
|
||||
AVFormatContext* m_fmt = nullptr;
|
||||
AVCodecContext* m_codec = nullptr;
|
||||
AVBufferRef* m_hwDeviceCtx = nullptr;
|
||||
int m_streamIndex = -1;
|
||||
|
||||
CUcontext m_cuCtx = nullptr;
|
||||
CUgraphicsResource m_resY = nullptr;
|
||||
CUgraphicsResource m_resUV = nullptr;
|
||||
QSize m_texSize;
|
||||
|
||||
uint32_t m_texY = 0;
|
||||
uint32_t m_texUV = 0;
|
||||
|
||||
int m_matrix = 1;
|
||||
int m_range = 0;
|
||||
int m_texComp = 1;
|
||||
QSize m_size;
|
||||
};
|
||||
@@ -1,355 +0,0 @@
|
||||
#include "wallpaperimage.hpp"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QImageReader>
|
||||
#include <QtConcurrent>
|
||||
#include <QQuickWindow>
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
QThreadPool* WallpaperImage::loadPool() {
|
||||
static QThreadPool* pool = [] {
|
||||
auto* p = new QThreadPool();
|
||||
p->setMaxThreadCount(4);
|
||||
return p;
|
||||
}();
|
||||
return pool;
|
||||
}
|
||||
|
||||
WallpaperImage::WallpaperImage(QQuickItem* parent) : QQuickItem(parent) {
|
||||
setFlag(ItemHasContents, true);
|
||||
|
||||
m_loadDebounceTimer.setSingleShot(true);
|
||||
connect(
|
||||
&m_loadDebounceTimer,
|
||||
&QTimer::timeout,
|
||||
this,
|
||||
&WallpaperImage::beginLoad);
|
||||
|
||||
connect(
|
||||
&m_imageWatcher,
|
||||
&QFutureWatcher<QImage>::finished,
|
||||
this,
|
||||
&WallpaperImage::handleImageLoaded);
|
||||
}
|
||||
|
||||
WallpaperImage::~WallpaperImage() {
|
||||
m_requestGeneration->fetch_add(1, std::memory_order_relaxed);
|
||||
m_imageWatcher.disconnect();
|
||||
|
||||
if (m_texture) delete m_texture;
|
||||
if (m_previousTexture) delete m_previousTexture;
|
||||
}
|
||||
|
||||
void WallpaperImage::setStatus(const Status s) {
|
||||
if (m_status == s) return;
|
||||
m_status = s;
|
||||
emit statusChanged();
|
||||
}
|
||||
|
||||
void WallpaperImage::setSource(const QUrl& source) {
|
||||
if (m_source == source) return;
|
||||
m_source = source;
|
||||
emit sourceChanged();
|
||||
|
||||
setStatus(Loading);
|
||||
m_loadDebounceTimer.start(80);
|
||||
}
|
||||
|
||||
void WallpaperImage::setScreenResolution(const QSize& screenResolution) {
|
||||
if (m_screenResolution == screenResolution) return;
|
||||
m_screenResolution = screenResolution;
|
||||
emit screenResolutionChanged();
|
||||
beginLoad();
|
||||
}
|
||||
|
||||
void WallpaperImage::setZoom(qreal zoom) {
|
||||
if (qFuzzyCompare(m_zoom, zoom)) return;
|
||||
m_zoom = zoom;
|
||||
emit zoomChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
void WallpaperImage::setFadeDuration(int ms) {
|
||||
if (ms < 0) ms = 0;
|
||||
if (m_fadeDuration == ms) return;
|
||||
m_fadeDuration = ms;
|
||||
emit fadeDurationChanged();
|
||||
}
|
||||
|
||||
void WallpaperImage::setCropX(qreal x) {
|
||||
if (qFuzzyCompare(m_cropX, x)) return;
|
||||
m_cropX = x;
|
||||
emit cropXChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
void WallpaperImage::setCropY(qreal y) {
|
||||
if (qFuzzyCompare(m_cropY, y)) return;
|
||||
m_cropY = y;
|
||||
emit cropYChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
void WallpaperImage::setCropWidth(qreal w) {
|
||||
if (w <= 0.0) w = 1.0;
|
||||
if (qFuzzyCompare(m_cropWidth, w)) return;
|
||||
m_cropWidth = w;
|
||||
emit cropWidthChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
void WallpaperImage::setCropHeight(qreal h) {
|
||||
if (h <= 0.0) h = 1.0;
|
||||
if (qFuzzyCompare(m_cropHeight, h)) return;
|
||||
m_cropHeight = h;
|
||||
emit cropHeightChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QString WallpaperImage::getCacheFilePath() const {
|
||||
if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString();
|
||||
|
||||
QString cachePath =
|
||||
QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) +
|
||||
"/zshell/imagecache";
|
||||
QDir().mkpath(cachePath);
|
||||
|
||||
QString id = m_source.toString() + "_" +
|
||||
QString::number(m_screenResolution.width()) + "x" +
|
||||
QString::number(m_screenResolution.height());
|
||||
QByteArray hash =
|
||||
QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex();
|
||||
|
||||
return cachePath + "/" + hash + ".png";
|
||||
}
|
||||
|
||||
void WallpaperImage::beginLoad() {
|
||||
if (m_source.isEmpty()) {
|
||||
setStatus(Null);
|
||||
return;
|
||||
}
|
||||
|
||||
const quint64 requestId =
|
||||
m_requestGeneration->fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
|
||||
QString cacheFile = getCacheFilePath();
|
||||
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile()
|
||||
: m_source.toString();
|
||||
if (sourceFile.startsWith("qrc:/")) sourceFile = sourceFile.mid(3);
|
||||
|
||||
QSize targetRes = m_screenResolution;
|
||||
auto generation = m_requestGeneration;
|
||||
|
||||
QFuture<QImage> future = QtConcurrent::run(
|
||||
loadPool(),
|
||||
[sourceFile, cacheFile, targetRes, requestId, generation]() -> QImage {
|
||||
auto stale = [&] {
|
||||
return generation->load(std::memory_order_relaxed) != requestId;
|
||||
};
|
||||
|
||||
if (stale()) return QImage();
|
||||
|
||||
if (!targetRes.isEmpty() && !cacheFile.isEmpty() &&
|
||||
QFileInfo::exists(cacheFile)) {
|
||||
QImage cached(cacheFile);
|
||||
if (!cached.isNull()) return cached;
|
||||
}
|
||||
|
||||
if (stale()) return QImage();
|
||||
|
||||
QImage original;
|
||||
QImageReader reader(sourceFile);
|
||||
if (!targetRes.isEmpty() &&
|
||||
reader.supportsOption(QImageIOHandler::Size)) {
|
||||
QSize actual = reader.size();
|
||||
if (actual.isValid() &&
|
||||
(actual.width() > targetRes.width() ||
|
||||
actual.height() > targetRes.height())) {
|
||||
QSize scaledSize =
|
||||
actual.scaled(targetRes, Qt::KeepAspectRatioByExpanding);
|
||||
reader.setScaledSize(scaledSize);
|
||||
}
|
||||
original = reader.read();
|
||||
} else {
|
||||
original = QImage(sourceFile);
|
||||
}
|
||||
if (original.isNull()) return QImage();
|
||||
|
||||
if (stale()) return QImage();
|
||||
|
||||
if (targetRes.isEmpty()) return original;
|
||||
|
||||
QImage scaled = (original.width() > targetRes.width() ||
|
||||
original.height() > targetRes.height())
|
||||
? original.scaled(
|
||||
targetRes,
|
||||
Qt::KeepAspectRatioByExpanding,
|
||||
Qt::SmoothTransformation)
|
||||
: original;
|
||||
|
||||
if (stale()) return scaled;
|
||||
|
||||
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
|
||||
return scaled;
|
||||
});
|
||||
|
||||
m_imageWatcher.setFuture(future);
|
||||
}
|
||||
|
||||
void WallpaperImage::handleImageLoaded() {
|
||||
QImage result = m_imageWatcher.result();
|
||||
if (result.isNull()) {
|
||||
setStatus(Error);
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingImage = result;
|
||||
m_hasPending = true;
|
||||
setStatus(Ready);
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF WallpaperImage::computeSourceRect(QSGTexture* texture) const {
|
||||
QSize texSize = texture->textureSize();
|
||||
|
||||
qreal cW = m_cropWidth / m_zoom;
|
||||
qreal cH = m_cropHeight / m_zoom;
|
||||
|
||||
QRectF reqRect(
|
||||
m_cropX * texSize.width(),
|
||||
m_cropY * texSize.height(),
|
||||
cW * texSize.width(),
|
||||
cH * texSize.height());
|
||||
|
||||
QRectF bounds = boundingRect();
|
||||
if (bounds.isEmpty() || reqRect.isEmpty()) return reqRect;
|
||||
|
||||
qreal targetRatio = bounds.width() / bounds.height();
|
||||
qreal reqRatio = reqRect.width() / reqRect.height();
|
||||
|
||||
QRectF sourceRect = reqRect;
|
||||
|
||||
if (reqRatio > targetRatio) {
|
||||
qreal newWidth = reqRect.height() * targetRatio;
|
||||
qreal xOffset = (reqRect.width() - newWidth) / 2.0;
|
||||
sourceRect.setX(reqRect.x() + xOffset);
|
||||
sourceRect.setWidth(newWidth);
|
||||
} else if (reqRatio < targetRatio) {
|
||||
qreal newHeight = reqRect.width() / targetRatio;
|
||||
qreal yOffset = (reqRect.height() - newHeight) / 2.0;
|
||||
sourceRect.setY(reqRect.y() + yOffset);
|
||||
sourceRect.setHeight(newHeight);
|
||||
}
|
||||
|
||||
return sourceRect;
|
||||
}
|
||||
|
||||
void WallpaperImage::configureImageNode(
|
||||
QSGImageNode* node, QSGTexture* texture) {
|
||||
node->setTexture(texture);
|
||||
node->setRect(boundingRect());
|
||||
node->setFiltering(QSGTexture::Linear);
|
||||
|
||||
QRectF sourceRect = computeSourceRect(texture);
|
||||
node->setSourceRect(sourceRect);
|
||||
|
||||
if (texture == m_texture) {
|
||||
QSize texSize = texture->textureSize();
|
||||
QRectF normalizedActual(
|
||||
sourceRect.x() / texSize.width(),
|
||||
sourceRect.y() / texSize.height(),
|
||||
sourceRect.width() / texSize.width(),
|
||||
sourceRect.height() / texSize.height());
|
||||
|
||||
bool changed = false;
|
||||
auto updateIfChanged = [&](qreal& dst, qreal value) {
|
||||
if (!qFuzzyCompare(dst, value)) {
|
||||
dst = value;
|
||||
changed = true;
|
||||
}
|
||||
};
|
||||
updateIfChanged(m_actualCropX, normalizedActual.x());
|
||||
updateIfChanged(m_actualCropY, normalizedActual.y());
|
||||
updateIfChanged(m_actualCropWidth, normalizedActual.width());
|
||||
updateIfChanged(m_actualCropHeight, normalizedActual.height());
|
||||
if (changed) emit actualCropChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QSGNode* WallpaperImage::updatePaintNode(
|
||||
QSGNode* oldNode, UpdatePaintNodeData*) {
|
||||
Q_UNUSED(oldNode);
|
||||
|
||||
if (m_hasPending) {
|
||||
qDebug() << "pending format" << m_pendingImage.format()
|
||||
<< double(m_pendingImage.sizeInBytes()) / 1048576.0 << "MB";
|
||||
if (m_previousTexture) {
|
||||
delete m_previousTexture;
|
||||
m_previousTexture = nullptr;
|
||||
}
|
||||
m_previousTexture = m_texture;
|
||||
m_texture = window()->createTextureFromImage(
|
||||
m_pendingImage, QQuickWindow::TextureIsOpaque);
|
||||
m_pendingImage = QImage();
|
||||
m_hasPending = false;
|
||||
|
||||
if (m_previousTexture && m_fadeDuration > 0) {
|
||||
m_fading = true;
|
||||
m_fadeTimer.restart();
|
||||
} else {
|
||||
if (m_previousTexture) {
|
||||
delete m_previousTexture;
|
||||
m_previousTexture = nullptr;
|
||||
}
|
||||
m_fading = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_texture) {
|
||||
return m_rootNode;
|
||||
}
|
||||
|
||||
if (!m_rootNode) {
|
||||
m_rootNode = new QSGNode();
|
||||
|
||||
m_newOpacityNode = new QSGOpacityNode();
|
||||
m_newImageNode = window()->createImageNode();
|
||||
m_newOpacityNode->appendChildNode(m_newImageNode);
|
||||
m_rootNode->appendChildNode(m_newOpacityNode);
|
||||
|
||||
m_oldOpacityNode = new QSGOpacityNode();
|
||||
m_oldImageNode = window()->createImageNode();
|
||||
m_oldOpacityNode->appendChildNode(m_oldImageNode);
|
||||
m_rootNode->appendChildNode(m_oldOpacityNode);
|
||||
}
|
||||
|
||||
configureImageNode(m_newImageNode, m_texture);
|
||||
m_newOpacityNode->setOpacity(1.0);
|
||||
|
||||
if (m_fading && m_previousTexture) {
|
||||
qreal progress =
|
||||
qBound(0.0, qreal(m_fadeTimer.elapsed()) / m_fadeDuration, 1.0);
|
||||
|
||||
configureImageNode(m_oldImageNode, m_previousTexture);
|
||||
m_oldOpacityNode->setOpacity(1.0 - progress);
|
||||
|
||||
if (progress >= 1.0) {
|
||||
m_fading = false;
|
||||
delete m_previousTexture;
|
||||
m_previousTexture = nullptr;
|
||||
m_oldOpacityNode->setOpacity(0.0);
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
} else {
|
||||
m_oldOpacityNode->setOpacity(0.0);
|
||||
}
|
||||
|
||||
return m_rootNode;
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -1,147 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QImage>
|
||||
#include <QUrl>
|
||||
#include <QSGTexture>
|
||||
#include <QSGOpacityNode>
|
||||
#include <QSGImageNode>
|
||||
#include <QFutureWatcher>
|
||||
#include <QElapsedTimer>
|
||||
#include <QThreadPool>
|
||||
#include <QTimer>
|
||||
#include <QtQml/qqml.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <atomic>
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
class WallpaperImage : public QQuickItem {
|
||||
Q_OBJECT
|
||||
QML_NAMED_ELEMENT(WallpaperImage)
|
||||
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
|
||||
Q_PROPERTY(
|
||||
QSize screenResolution READ screenResolution WRITE setScreenResolution
|
||||
NOTIFY screenResolutionChanged)
|
||||
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
|
||||
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
||||
Q_PROPERTY(
|
||||
int fadeDuration READ fadeDuration WRITE setFadeDuration NOTIFY
|
||||
fadeDurationChanged)
|
||||
|
||||
Q_PROPERTY(qreal actualCropX READ actualCropX NOTIFY actualCropChanged)
|
||||
Q_PROPERTY(qreal actualCropY READ actualCropY NOTIFY actualCropChanged)
|
||||
Q_PROPERTY(
|
||||
qreal actualCropWidth READ actualCropWidth NOTIFY actualCropChanged)
|
||||
Q_PROPERTY(
|
||||
qreal actualCropHeight READ actualCropHeight NOTIFY actualCropChanged)
|
||||
|
||||
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
|
||||
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
|
||||
Q_PROPERTY(
|
||||
qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY
|
||||
cropWidthChanged)
|
||||
Q_PROPERTY(
|
||||
qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY
|
||||
cropHeightChanged)
|
||||
|
||||
public:
|
||||
explicit WallpaperImage(QQuickItem* parent = nullptr);
|
||||
~WallpaperImage() override;
|
||||
|
||||
enum Status { Null, Ready, Loading, Error };
|
||||
Q_ENUM(Status)
|
||||
|
||||
Status status() const { return m_status; }
|
||||
|
||||
QUrl source() const { return m_source; }
|
||||
void setSource(const QUrl& source);
|
||||
|
||||
QSize screenResolution() const { return m_screenResolution; }
|
||||
void setScreenResolution(const QSize& screenResolution);
|
||||
|
||||
qreal zoom() const { return m_zoom; }
|
||||
void setZoom(qreal zoom);
|
||||
|
||||
int fadeDuration() const { return m_fadeDuration; }
|
||||
void setFadeDuration(int ms);
|
||||
|
||||
qreal actualCropX() const { return m_actualCropX; }
|
||||
qreal actualCropY() const { return m_actualCropY; }
|
||||
qreal actualCropWidth() const { return m_actualCropWidth; }
|
||||
qreal actualCropHeight() const { return m_actualCropHeight; }
|
||||
|
||||
qreal cropX() const { return m_cropX; }
|
||||
void setCropX(qreal x);
|
||||
|
||||
qreal cropY() const { return m_cropY; }
|
||||
void setCropY(qreal y);
|
||||
|
||||
qreal cropWidth() const { return m_cropWidth; }
|
||||
void setCropWidth(qreal w);
|
||||
|
||||
qreal cropHeight() const { return m_cropHeight; }
|
||||
void setCropHeight(qreal h);
|
||||
|
||||
protected:
|
||||
QSGNode* updatePaintNode(
|
||||
QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData) override;
|
||||
|
||||
signals:
|
||||
void sourceChanged();
|
||||
void screenResolutionChanged();
|
||||
void zoomChanged();
|
||||
void fadeDurationChanged();
|
||||
void actualCropChanged();
|
||||
void cropXChanged();
|
||||
void cropYChanged();
|
||||
void cropWidthChanged();
|
||||
void cropHeightChanged();
|
||||
void statusChanged();
|
||||
|
||||
private:
|
||||
void beginLoad();
|
||||
void handleImageLoaded();
|
||||
QString getCacheFilePath() const;
|
||||
void setStatus(const Status s);
|
||||
QRectF computeSourceRect(QSGTexture* texture) const;
|
||||
void configureImageNode(QSGImageNode* node, QSGTexture* texture);
|
||||
|
||||
Status m_status = Null;
|
||||
QUrl m_source;
|
||||
QSize m_screenResolution;
|
||||
qreal m_zoom = 1.0;
|
||||
int m_fadeDuration = 400;
|
||||
|
||||
qreal m_actualCropX = 0.0;
|
||||
qreal m_actualCropY = 0.0;
|
||||
qreal m_actualCropWidth = 1.0;
|
||||
qreal m_actualCropHeight = 1.0;
|
||||
|
||||
qreal m_cropX = 0.0;
|
||||
qreal m_cropY = 0.0;
|
||||
qreal m_cropWidth = 1.0;
|
||||
qreal m_cropHeight = 1.0;
|
||||
|
||||
QTimer m_loadDebounceTimer;
|
||||
QFutureWatcher<QImage> m_imageWatcher;
|
||||
std::shared_ptr<std::atomic<quint64>> m_requestGeneration =
|
||||
std::make_shared<std::atomic<quint64>>(0);
|
||||
static QThreadPool* loadPool();
|
||||
QImage m_pendingImage;
|
||||
bool m_hasPending = false;
|
||||
|
||||
QSGTexture* m_texture = nullptr;
|
||||
QSGTexture* m_previousTexture = nullptr;
|
||||
|
||||
bool m_fading = false;
|
||||
QElapsedTimer m_fadeTimer;
|
||||
|
||||
QSGNode* m_rootNode = nullptr;
|
||||
QSGOpacityNode* m_newOpacityNode = nullptr;
|
||||
QSGOpacityNode* m_oldOpacityNode = nullptr;
|
||||
QSGImageNode* m_newImageNode = nullptr;
|
||||
QSGImageNode* m_oldImageNode = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -1,121 +0,0 @@
|
||||
#include "wallpaperobject.hpp"
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
WallpaperObject::WallpaperObject(QObject* parent) : QObject(parent) {}
|
||||
|
||||
WallpaperObject::~WallpaperObject() {
|
||||
if (m_service && !m_screen.isEmpty()) m_service->removeScreen(m_screen);
|
||||
}
|
||||
|
||||
void WallpaperObject::registerScreen() {
|
||||
if (!m_service || m_screen.isEmpty()) return;
|
||||
|
||||
m_service->addScreen(m_screen);
|
||||
}
|
||||
|
||||
void WallpaperObject::setService(WallpaperService* service) {
|
||||
if (m_service == service) return;
|
||||
m_service = service;
|
||||
emit serviceChanged();
|
||||
|
||||
registerScreen();
|
||||
push();
|
||||
}
|
||||
|
||||
void WallpaperObject::setScreen(const QString& screen) {
|
||||
if (m_screen == screen) return;
|
||||
m_screen = screen;
|
||||
emit screenChanged();
|
||||
|
||||
registerScreen();
|
||||
push();
|
||||
}
|
||||
|
||||
void WallpaperObject::setCropX(qreal x) {
|
||||
if (qFuzzyCompare(m_cropX, x)) return;
|
||||
m_cropX = x;
|
||||
emit cropXChanged();
|
||||
push();
|
||||
}
|
||||
|
||||
void WallpaperObject::setCropY(qreal y) {
|
||||
if (qFuzzyCompare(m_cropY, y)) return;
|
||||
m_cropY = y;
|
||||
emit cropYChanged();
|
||||
push();
|
||||
}
|
||||
|
||||
void WallpaperObject::setCropWidth(qreal w) {
|
||||
if (qFuzzyCompare(m_cropWidth, w)) return;
|
||||
m_cropWidth = w;
|
||||
emit cropWidthChanged();
|
||||
push();
|
||||
}
|
||||
|
||||
void WallpaperObject::setCropHeight(qreal h) {
|
||||
if (qFuzzyCompare(m_cropHeight, h)) return;
|
||||
m_cropHeight = h;
|
||||
emit cropHeightChanged();
|
||||
push();
|
||||
}
|
||||
|
||||
void WallpaperObject::setZoom(qreal zoom) {
|
||||
if (qFuzzyCompare(m_zoom, zoom)) return;
|
||||
m_zoom = zoom;
|
||||
emit zoomChanged();
|
||||
push();
|
||||
}
|
||||
|
||||
void WallpaperObject::syncCrop(const QVariantMap& crop) {
|
||||
const qreal x = crop.value("x").toDouble();
|
||||
const qreal y = crop.value("y").toDouble();
|
||||
const qreal width = crop.contains("width") ? crop.value("width").toDouble()
|
||||
: 1.0;
|
||||
const qreal height = crop.contains("height")
|
||||
? crop.value("height").toDouble()
|
||||
: 1.0;
|
||||
const qreal z =
|
||||
crop.contains("zoom") ? crop.value("zoom").toDouble() : 1.0;
|
||||
|
||||
const bool prev = m_pushEnabled;
|
||||
m_pushEnabled = false;
|
||||
|
||||
if (!qFuzzyCompare(m_cropX, x)) {
|
||||
m_cropX = x;
|
||||
emit cropXChanged();
|
||||
}
|
||||
if (!qFuzzyCompare(m_cropY, y)) {
|
||||
m_cropY = y;
|
||||
emit cropYChanged();
|
||||
}
|
||||
if (!qFuzzyCompare(m_cropWidth, width)) {
|
||||
m_cropWidth = width;
|
||||
emit cropWidthChanged();
|
||||
}
|
||||
if (!qFuzzyCompare(m_cropHeight, height)) {
|
||||
m_cropHeight = height;
|
||||
emit cropHeightChanged();
|
||||
}
|
||||
if (!qFuzzyCompare(m_zoom, z)) {
|
||||
m_zoom = z;
|
||||
emit zoomChanged();
|
||||
}
|
||||
|
||||
m_pushEnabled = prev;
|
||||
}
|
||||
|
||||
void WallpaperObject::push() {
|
||||
if (!m_pushEnabled || !m_service || m_screen.isEmpty()) return;
|
||||
|
||||
QVariantMap crop;
|
||||
crop.insert("x", m_cropX);
|
||||
crop.insert("y", m_cropY);
|
||||
crop.insert("width", m_cropWidth);
|
||||
crop.insert("height", m_cropHeight);
|
||||
crop.insert("zoom", m_zoom);
|
||||
|
||||
m_service->setCrop(m_screen, crop);
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
#include <QtQml/qqml.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "wallpaperservice.hpp"
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
class WallpaperObject : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(
|
||||
WallpaperService* service READ service WRITE setService NOTIFY
|
||||
serviceChanged)
|
||||
Q_PROPERTY(QString screen READ screen WRITE setScreen NOTIFY screenChanged)
|
||||
|
||||
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
|
||||
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
|
||||
Q_PROPERTY(
|
||||
qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY
|
||||
cropWidthChanged)
|
||||
Q_PROPERTY(
|
||||
qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY
|
||||
cropHeightChanged)
|
||||
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
|
||||
|
||||
public:
|
||||
explicit WallpaperObject(QObject* parent = nullptr);
|
||||
~WallpaperObject() override;
|
||||
|
||||
WallpaperService* service() const { return m_service; }
|
||||
void setService(WallpaperService* service);
|
||||
|
||||
QString screen() const { return m_screen; }
|
||||
void setScreen(const QString& screen);
|
||||
|
||||
qreal cropX() const { return m_cropX; }
|
||||
void setCropX(qreal x);
|
||||
|
||||
qreal cropY() const { return m_cropY; }
|
||||
void setCropY(qreal y);
|
||||
|
||||
qreal cropWidth() const { return m_cropWidth; }
|
||||
void setCropWidth(qreal w);
|
||||
|
||||
qreal cropHeight() const { return m_cropHeight; }
|
||||
void setCropHeight(qreal h);
|
||||
|
||||
qreal zoom() const { return m_zoom; }
|
||||
void setZoom(qreal zoom);
|
||||
|
||||
Q_INVOKABLE void syncCrop(const QVariantMap& crop);
|
||||
|
||||
signals:
|
||||
void serviceChanged();
|
||||
void screenChanged();
|
||||
void cropXChanged();
|
||||
void cropYChanged();
|
||||
void cropWidthChanged();
|
||||
void cropHeightChanged();
|
||||
void zoomChanged();
|
||||
|
||||
private:
|
||||
void registerScreen();
|
||||
|
||||
void push();
|
||||
|
||||
QPointer<WallpaperService> m_service;
|
||||
QString m_screen;
|
||||
|
||||
qreal m_cropX = 0.0;
|
||||
qreal m_cropY = 0.0;
|
||||
qreal m_cropWidth = 1.0;
|
||||
qreal m_cropHeight = 1.0;
|
||||
qreal m_zoom = 1.0;
|
||||
|
||||
bool m_pushEnabled = true;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,384 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <QImage>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QElapsedTimer>
|
||||
#include <QRectF>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVariantMap>
|
||||
#include <QVector>
|
||||
#include <QtQml/qqml.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
#include "videoplayer.hpp"
|
||||
#endif
|
||||
|
||||
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
#include "wallpapersurface.hpp"
|
||||
#include "config/enums.hpp"
|
||||
|
||||
struct mpv_handle;
|
||||
struct mpv_render_context;
|
||||
|
||||
class QProcess;
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
class WallpaperSurface;
|
||||
|
||||
class WallpaperService : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
||||
Q_PROPERTY(QVariantMap crops READ crops WRITE setCrops NOTIFY cropsChanged)
|
||||
Q_PROPERTY(QString dbPath READ dbPath WRITE setDbPath NOTIFY dbPathChanged)
|
||||
Q_PROPERTY(
|
||||
QSize largestBufferSize READ largestBufferSize NOTIFY
|
||||
largestBufferSizeChanged)
|
||||
|
||||
public:
|
||||
explicit WallpaperService(QObject* parent = nullptr);
|
||||
~WallpaperService() override;
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(const QString& path);
|
||||
|
||||
[[nodiscard]] QSize largestBufferSize() const;
|
||||
|
||||
[[nodiscard]] QString dbPath() const;
|
||||
void setDbPath(const QString& path);
|
||||
|
||||
void addScreen(const QString& screen);
|
||||
void removeScreen(const QString& screen);
|
||||
|
||||
[[nodiscard]] const QVariantMap& crops() const;
|
||||
void setCrops(const QVariantMap& crops);
|
||||
|
||||
Q_INVOKABLE void redraw();
|
||||
|
||||
Q_INVOKABLE void setCrop(const QString& screen, const QVariantMap& crop);
|
||||
|
||||
Q_INVOKABLE void invalidateCache();
|
||||
|
||||
signals:
|
||||
void pathChanged();
|
||||
void cropsChanged();
|
||||
void dbPathChanged();
|
||||
void largestBufferSizeChanged();
|
||||
|
||||
private:
|
||||
friend class WallpaperSurface;
|
||||
|
||||
static bool isVideoPath(const QString& path);
|
||||
|
||||
static bool isHardwareCodec(const QString& codec);
|
||||
|
||||
[[nodiscard]] QString codecName(const QString& path) const;
|
||||
[[nodiscard]] QString cacheDir() const;
|
||||
[[nodiscard]] QString transcodeTarget(const QString& path) const;
|
||||
[[nodiscard]] bool needsTranscode(const QString& path) const;
|
||||
void transcodeVideo(const QString& src, const QString& dst);
|
||||
|
||||
void applyPath(const QString& path);
|
||||
|
||||
void startVideo(const QString& path);
|
||||
void teardownMpv();
|
||||
|
||||
void initMpvRender();
|
||||
void renderMpvToSurface(const QSize& size);
|
||||
void pumpMpvEvents();
|
||||
|
||||
static void* getMpvProcAddress(void* ctx, const char* name);
|
||||
static void mpvUpdate(void* ctx);
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
bool startPlayerVideo(const QString& path);
|
||||
void stopPlayerVideo();
|
||||
[[nodiscard]] uint32_t createVideoProgram();
|
||||
void drawVideoTexture(
|
||||
uint32_t lumaTex,
|
||||
uint32_t chromaTex,
|
||||
int matrix,
|
||||
int range,
|
||||
const QRectF& rect,
|
||||
float sizeW,
|
||||
float sizeH,
|
||||
float srcW,
|
||||
float srcH,
|
||||
float alpha);
|
||||
#endif
|
||||
|
||||
void bindGlobals();
|
||||
static void registryGlobal(
|
||||
void* data,
|
||||
struct wl_registry* registry,
|
||||
uint32_t name,
|
||||
const char* interface,
|
||||
uint32_t version);
|
||||
|
||||
void storeGlobal(const char* interface, void* proxy);
|
||||
|
||||
struct TransProgram {
|
||||
uint32_t id = 0;
|
||||
int size = -1;
|
||||
int progress = -1;
|
||||
int from = -1;
|
||||
int to = -1;
|
||||
int fromCrop = -1;
|
||||
int toCrop = -1;
|
||||
int fromFlip = -1;
|
||||
int toFlip = -1;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool setupGl();
|
||||
[[nodiscard]] uint32_t createProgram();
|
||||
[[nodiscard]] TransProgram createTransitionProgram(const QString& file);
|
||||
[[nodiscard]] TransProgram programFor(const QString& file);
|
||||
bool ensureTransitionProgram();
|
||||
QString transitionFileFor(ZShell::config::WallpaperTransition::Enum value);
|
||||
[[nodiscard]] QString randomTransitionFile();
|
||||
|
||||
void ensureCapture(const QSize& size);
|
||||
uint32_t renderVideoIntoTexture(const QSize& size);
|
||||
|
||||
void drawSurfaceTransition(WallpaperSurface* surface);
|
||||
|
||||
struct TransitionLayer {
|
||||
uint32_t tex = 0; // frozen image target (older layers)
|
||||
QImage image; // CPU copy for crops/sizing (image layers)
|
||||
QVariantMap crops; // crop map captured when the layer was pushed
|
||||
QString transitionFile; // this layer's own transition shader
|
||||
bool isVideo = false; // only the newest layer may be video
|
||||
bool ready = false; // target is sampleable
|
||||
bool started = false; // its own clock has begun
|
||||
QElapsedTimer timer;
|
||||
qreal progress = 0.0;
|
||||
};
|
||||
|
||||
void ensureBaseFbo();
|
||||
void ensureAccum(const QSize& size);
|
||||
void bindRtt(uint32_t fbo, uint32_t tex, const QSize& size);
|
||||
void drawTransitionPair(
|
||||
const TransProgram& program,
|
||||
uint32_t fromTex,
|
||||
float fromFlip,
|
||||
const QRectF& fromCrop,
|
||||
uint32_t toTex,
|
||||
float toFlip,
|
||||
const QRectF& toCrop,
|
||||
qreal progress,
|
||||
const QSize& size);
|
||||
uint32_t targetTexFor(const TransitionLayer& layer, int index) const;
|
||||
QRectF cropForLayer(
|
||||
const TransitionLayer& layer,
|
||||
const QString& screen,
|
||||
const QSize& size) const;
|
||||
void captureBase(
|
||||
const QSize& size,
|
||||
const QString& screen,
|
||||
uint32_t oldTex,
|
||||
float oldFlip,
|
||||
const QRectF& oldCrop);
|
||||
void renderComposite(
|
||||
const QSize& size,
|
||||
const QString& screen,
|
||||
uint32_t outFbo,
|
||||
uint32_t outTex,
|
||||
int first,
|
||||
int last,
|
||||
uint32_t videoTex,
|
||||
uint32_t posterTex);
|
||||
|
||||
void uploadTexture(uint32_t& texture, const QImage& image);
|
||||
void uploadImage();
|
||||
void createBlackTexture(uint32_t& texture);
|
||||
|
||||
void drawTexture(
|
||||
uint32_t texture,
|
||||
const QRectF& rect,
|
||||
float sizeW,
|
||||
float sizeH,
|
||||
float alpha);
|
||||
|
||||
[[nodiscard]] QImage decodeImage(
|
||||
const QString& path, const QSize& decodeSize) const;
|
||||
|
||||
[[nodiscard]] QByteArray cachedBlob(const QString& path) const;
|
||||
|
||||
[[nodiscard]] QByteArray posterBlob(const QString& path) const;
|
||||
|
||||
[[nodiscard]] QVariantMap cropsForPath(const QString& path) const;
|
||||
|
||||
void ensureImage();
|
||||
|
||||
void ensurePoster();
|
||||
|
||||
void notifyBufferSizes();
|
||||
|
||||
void queue(WallpaperSurface* surface);
|
||||
void queueAll();
|
||||
|
||||
void loop();
|
||||
|
||||
void present();
|
||||
|
||||
void drawSurface(WallpaperSurface* surface);
|
||||
|
||||
void wake();
|
||||
|
||||
[[nodiscard]] wl_event_queue* eventQueue() const { return m_queue; }
|
||||
|
||||
[[nodiscard]] QRectF sourceRect(
|
||||
const QVariantMap& crops,
|
||||
const QString& screen,
|
||||
const QSize& size,
|
||||
const QSize& image) const;
|
||||
|
||||
void start();
|
||||
|
||||
void addSurfaces();
|
||||
void addSurface(const QString& screen);
|
||||
|
||||
QString m_path;
|
||||
QString m_dbPath;
|
||||
QStringList m_screens;
|
||||
QVariantMap m_crops;
|
||||
|
||||
QImage m_image;
|
||||
|
||||
bool m_isVideo = false;
|
||||
bool m_videoReload = false;
|
||||
mpv_handle* m_mpv = nullptr;
|
||||
mpv_render_context* m_mpvRender = nullptr;
|
||||
uint32_t m_blackTex = 0;
|
||||
std::atomic<bool> m_mpvFrameReady{false};
|
||||
std::atomic<bool> m_videoReady{false};
|
||||
std::atomic<bool> m_videoLoaded{false};
|
||||
|
||||
QProcess* m_transcode = nullptr;
|
||||
|
||||
QSize m_decodeSize;
|
||||
|
||||
QImage m_stagedImage;
|
||||
QString m_stagedPath;
|
||||
bool m_stagedReady = false;
|
||||
bool m_decoding = false;
|
||||
QString m_decodePath;
|
||||
bool m_replacementReady = false;
|
||||
|
||||
wl_display* m_display = nullptr;
|
||||
wl_registry* m_registry = nullptr;
|
||||
wl_compositor* m_compositor = nullptr;
|
||||
QtWayland::zwlr_layer_shell_v1* m_layerShell = nullptr;
|
||||
QtWayland::wp_viewporter* m_viewporter = nullptr;
|
||||
QtWayland::wp_fractional_scale_manager_v1* m_scaleManager = nullptr;
|
||||
|
||||
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
||||
EGLConfig m_config = nullptr;
|
||||
EGLContext m_context = EGL_NO_CONTEXT;
|
||||
uint32_t m_texture = 0;
|
||||
uint32_t m_program = 0;
|
||||
int m_sizeUniform = -1;
|
||||
int m_cropUniform = -1;
|
||||
int m_alphaUniform = -1;
|
||||
int m_sourceUniform = -1;
|
||||
|
||||
bool m_randomTransition = false; // wallTransition == Random
|
||||
QString m_transitionFile; // the transition stamped onto new layers
|
||||
bool m_transitionDirty = false;
|
||||
QHash<QString, TransProgram>
|
||||
m_transPrograms; // compiled programs, keyed by shader
|
||||
TransProgram
|
||||
m_neutralProgram; // fade program for identity copies (seed/base/final)
|
||||
QVector<int>
|
||||
m_transitionUses; // usage counts, parallel to the transition list
|
||||
QString m_lastTransition; // last random pick, so it is never repeated
|
||||
|
||||
uint32_t m_captureFbo = 0;
|
||||
uint32_t m_captureTex = 0;
|
||||
QSize m_captureSize;
|
||||
|
||||
uint32_t m_baseFbo = 0;
|
||||
uint32_t m_baseTex = 0;
|
||||
QSize m_baseSize;
|
||||
bool m_baseReady = false;
|
||||
|
||||
uint32_t m_accumFbo = 0;
|
||||
uint32_t m_accumA = 0;
|
||||
uint32_t m_accumB = 0;
|
||||
QSize m_accumSize;
|
||||
|
||||
std::vector<TransitionLayer> m_transitions; // oldest .. newest
|
||||
|
||||
bool m_needCaptureBase = false;
|
||||
QImage m_baseImage; // old settled image, for the base capture crop
|
||||
QVariantMap m_baseCrops; // old settled crop map, for the base capture crop
|
||||
bool m_baseIsVideo = false; // old settled frame was a live video
|
||||
bool m_baseIsBlack = false; // shell start / first load: fade in from black
|
||||
bool m_collapseVideo =
|
||||
false; // video pushed mid-stack: bake older into base
|
||||
bool m_promoteOldest = false; // stack over the cap: promote oldest to base
|
||||
bool m_newestDone = false; // newest layer finished: settle into base
|
||||
|
||||
static constexpr int kMaxTransitions = 4;
|
||||
|
||||
uint32_t m_videoProgram = 0;
|
||||
uint32_t m_vao = 0;
|
||||
int m_videoSizeUniform = -1;
|
||||
int m_videoSrcSizeUniform = -1;
|
||||
int m_videoCropUniform = -1;
|
||||
int m_videoAlphaUniform = -1;
|
||||
int m_videoLumaUniform = -1;
|
||||
int m_videoChromaUniform = -1;
|
||||
int m_videoMatrixUniform = -1;
|
||||
int m_videoRangeUniform = -1;
|
||||
int m_videoChromaOffUniform = -1;
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
std::unique_ptr<VideoPlayer> m_player;
|
||||
bool m_usePlayer = true;
|
||||
#endif
|
||||
|
||||
std::vector<uint32_t> m_orphanTextures;
|
||||
|
||||
QList<WallpaperSurface*> m_surfaces;
|
||||
|
||||
std::thread m_thread;
|
||||
mutable std::recursive_mutex m_mutex;
|
||||
wl_event_queue* m_queue = nullptr;
|
||||
|
||||
int m_wake[2] = {-1, -1};
|
||||
|
||||
bool m_stop = false;
|
||||
bool m_drawing = false;
|
||||
|
||||
bool started = false;
|
||||
|
||||
bool m_forceDecode = false;
|
||||
bool m_imageChanged = false;
|
||||
|
||||
QImage m_posterImage;
|
||||
uint32_t m_posterTex = 0;
|
||||
QString m_posterPath;
|
||||
bool m_posterReady = false;
|
||||
bool m_posterLoading = false;
|
||||
bool m_posterChanged = false;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -1,191 +0,0 @@
|
||||
#include "wallpapersurface.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "wallpaperservice.hpp"
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
const QString kNamespace = "ZShell-Wallpaper";
|
||||
|
||||
void setQueue(void* proxy, struct wl_event_queue* queue) {
|
||||
wl_proxy_set_queue(static_cast<struct wl_proxy*>(proxy), queue);
|
||||
}
|
||||
|
||||
WallpaperSurface::WallpaperSurface(
|
||||
WallpaperService* service,
|
||||
wl_surface* surface,
|
||||
wl_output* output,
|
||||
const QString& screen)
|
||||
: m_service(service), m_screen(screen), m_surface(surface) {
|
||||
if (!surface) {
|
||||
qWarning() << "WallpaperSurface: no surface for" << screen;
|
||||
return;
|
||||
}
|
||||
|
||||
setQueue(m_surface, m_service->eventQueue());
|
||||
|
||||
auto* layer = m_service->m_layerShell->get_layer_surface(
|
||||
m_surface,
|
||||
output,
|
||||
QtWayland::zwlr_layer_shell_v1::layer_background,
|
||||
kNamespace);
|
||||
setQueue(layer, m_service->eventQueue());
|
||||
QtWayland::zwlr_layer_surface_v1::init(layer);
|
||||
|
||||
set_size(0, 0);
|
||||
set_anchor(anchor_top | anchor_bottom | anchor_left | anchor_right);
|
||||
set_exclusive_zone(-1);
|
||||
set_keyboard_interactivity(keyboard_interactivity_none);
|
||||
set_margin(0, 0, 0, 0);
|
||||
|
||||
auto* viewport = m_service->m_viewporter->get_viewport(m_surface);
|
||||
setQueue(viewport, m_service->eventQueue());
|
||||
QtWayland::wp_viewport::init(viewport);
|
||||
|
||||
auto* scale = m_service->m_scaleManager->get_fractional_scale(m_surface);
|
||||
setQueue(scale, m_service->eventQueue());
|
||||
QtWayland::wp_fractional_scale_v1::init(scale);
|
||||
|
||||
wl_surface_commit(m_surface);
|
||||
}
|
||||
|
||||
WallpaperSurface::~WallpaperSurface() {
|
||||
if (m_frameCallback) {
|
||||
wl_callback_destroy(m_frameCallback);
|
||||
m_frameCallback = nullptr;
|
||||
}
|
||||
|
||||
if (m_eglSurface != EGL_NO_SURFACE)
|
||||
eglDestroySurface(m_service->m_eglDisplay, m_eglSurface);
|
||||
|
||||
if (m_window) wl_egl_window_destroy(m_window);
|
||||
|
||||
if (QtWayland::wp_viewport::isInitialized())
|
||||
QtWayland::wp_viewport::destroy();
|
||||
if (QtWayland::wp_fractional_scale_v1::isInitialized())
|
||||
QtWayland::wp_fractional_scale_v1::destroy();
|
||||
if (QtWayland::zwlr_layer_surface_v1::isInitialized())
|
||||
QtWayland::zwlr_layer_surface_v1::destroy();
|
||||
|
||||
if (m_surface) wl_surface_destroy(m_surface);
|
||||
}
|
||||
|
||||
void WallpaperSurface::markDirty() {
|
||||
m_dirty.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool WallpaperSurface::needsDraw() const {
|
||||
return m_dirty.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool WallpaperSurface::frameReady() const {
|
||||
return m_frameReady.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WallpaperSurface::requestFrame() {
|
||||
if (!m_surface || m_frameCallback) return;
|
||||
|
||||
m_frameCallback = wl_surface_frame(m_surface);
|
||||
|
||||
static const struct wl_callback_listener listener = {
|
||||
[](void* data, struct wl_callback*, uint32_t) {
|
||||
static_cast<WallpaperSurface*>(data)->onFrameDone();
|
||||
},
|
||||
};
|
||||
|
||||
wl_callback_add_listener(m_frameCallback, &listener, this);
|
||||
}
|
||||
|
||||
void WallpaperSurface::onFrameDone() {
|
||||
if (m_frameCallback) {
|
||||
wl_callback_destroy(m_frameCallback);
|
||||
m_frameCallback = nullptr;
|
||||
}
|
||||
|
||||
m_frameReady.store(true, std::memory_order_relaxed);
|
||||
m_service->wake();
|
||||
}
|
||||
|
||||
void WallpaperSurface::endDraw() {
|
||||
m_dirty.store(false, std::memory_order_relaxed);
|
||||
m_frameReady.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WallpaperSurface::zwlr_layer_surface_v1_configure(
|
||||
uint32_t serial, uint32_t width, uint32_t height) {
|
||||
ack_configure(serial);
|
||||
|
||||
if (width == 0 || height == 0) return;
|
||||
|
||||
m_size = QSize(int(width), int(height));
|
||||
|
||||
if (!configured) configured = true;
|
||||
|
||||
updateBuffer();
|
||||
}
|
||||
|
||||
void WallpaperSurface::wp_fractional_scale_v1_preferred_scale(uint32_t scale) {
|
||||
if (scale == 0) return;
|
||||
|
||||
qreal newScale = qreal(scale) / 120.0;
|
||||
if (qFuzzyCompare(newScale, m_scale)) return;
|
||||
|
||||
m_scale = newScale;
|
||||
updateBuffer();
|
||||
}
|
||||
|
||||
void WallpaperSurface::zwlr_layer_surface_v1_closed() {}
|
||||
|
||||
void WallpaperSurface::updateBuffer() {
|
||||
if (m_size.isEmpty()) return;
|
||||
|
||||
QSize buffer = QSize(
|
||||
int(std::llround(m_size.width() * m_scale)),
|
||||
int(std::llround(m_size.height() * m_scale)));
|
||||
|
||||
if (buffer.isEmpty()) return;
|
||||
|
||||
if (m_bufferSize != buffer) {
|
||||
m_bufferSize = buffer;
|
||||
|
||||
if (m_window) {
|
||||
wl_egl_window_resize(
|
||||
m_window, buffer.width(), buffer.height(), 0, 0);
|
||||
} else {
|
||||
m_window =
|
||||
wl_egl_window_create(m_surface, buffer.width(), buffer.height());
|
||||
if (!m_window) {
|
||||
qWarning()
|
||||
<< "WallpaperSurface: could not make a window surface for"
|
||||
<< m_screen;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_eglSurface == EGL_NO_SURFACE) {
|
||||
m_eglSurface = eglCreateWindowSurface(
|
||||
m_service->m_eglDisplay,
|
||||
m_service->m_config,
|
||||
reinterpret_cast<EGLNativeWindowType>(m_window),
|
||||
nullptr);
|
||||
if (m_eglSurface == EGL_NO_SURFACE) {
|
||||
qWarning()
|
||||
<< "WallpaperSurface: eglCreateWindowSurface failed for"
|
||||
<< m_screen;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (QtWayland::wp_viewport::isInitialized())
|
||||
set_destination(m_size.width(), m_size.height());
|
||||
|
||||
requestFrame();
|
||||
m_service->queue(this);
|
||||
m_service->notifyBufferSizes();
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -1,73 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-egl.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
|
||||
#include <qwayland-fractional-scale-v1.h>
|
||||
#include <qwayland-viewporter.h>
|
||||
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
class WallpaperService;
|
||||
|
||||
class WallpaperSurface : public QtWayland::zwlr_layer_surface_v1,
|
||||
public QtWayland::wp_viewport,
|
||||
public QtWayland::wp_fractional_scale_v1 {
|
||||
public:
|
||||
WallpaperSurface(
|
||||
WallpaperService* service,
|
||||
wl_surface* surface,
|
||||
wl_output* output,
|
||||
const QString& screen);
|
||||
~WallpaperSurface() override;
|
||||
|
||||
[[nodiscard]] const QString& screen() const { return m_screen; }
|
||||
|
||||
[[nodiscard]] const QSize& size() const { return m_size; }
|
||||
[[nodiscard]] const QSize& bufferSize() const { return m_bufferSize; }
|
||||
[[nodiscard]] EGLSurface eglSurface() const { return m_eglSurface; }
|
||||
|
||||
void markDirty();
|
||||
[[nodiscard]] bool needsDraw() const;
|
||||
|
||||
[[nodiscard]] bool frameReady() const;
|
||||
|
||||
void requestFrame();
|
||||
|
||||
void endDraw();
|
||||
|
||||
private:
|
||||
void zwlr_layer_surface_v1_configure(
|
||||
uint32_t serial, uint32_t width, uint32_t height) override;
|
||||
void zwlr_layer_surface_v1_closed() override;
|
||||
void wp_fractional_scale_v1_preferred_scale(uint32_t scale) override;
|
||||
|
||||
void updateBuffer();
|
||||
|
||||
void onFrameDone();
|
||||
|
||||
WallpaperService* m_service;
|
||||
QString m_screen;
|
||||
wl_surface* m_surface;
|
||||
wl_egl_window* m_window = nullptr;
|
||||
EGLSurface m_eglSurface = EGL_NO_SURFACE;
|
||||
|
||||
QSize m_size;
|
||||
QSize m_bufferSize;
|
||||
qreal m_scale = 1.0;
|
||||
|
||||
bool configured = false;
|
||||
|
||||
std::atomic<bool> m_dirty{false};
|
||||
std::atomic<bool> m_frameReady{true};
|
||||
struct wl_callback* m_frameCallback = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
Reference in New Issue
Block a user