OpenXR: per-screen placement for headset screens, and Edit Screens to arrange them by hand (#19945)

* video: per-screen geometry for the headset's floating screens

Each floating screen, by the core's logical screen id, gets its own
offsets, yaw, pitch, roll, scale and visibility (video_screen_xr_t),
applied on top of where the layout puts it. video_views_xr_reset gives
the defaults and video_views_xr_normalize clamps each value to its
range: offsets to 10 m either way, angles to 180 degrees, scale from 0.1
to 10. A planner given no screens uses the defaults, so nothing moves
until a setting does.

* configuration: save per-screen headset geometry

video_screen<N>_xr_x, _y, _z, _yaw, _pitch, _roll, _scale and _visible
keep each floating screen's adjustments for screen ids 1 to 8, and
survive configuration overrides like the other video settings. A minimal
save writes only the values that differ from the defaults.

The video_screens settings sample checks the round trip and the override
cases, and runs in the OpenXR input and Wine views jobs. The defaults
fixture now links video_views.c, which the reset uses.

* menu: per-screen controls for the headset's floating screens

Stereo 3D & Screens gains Screen, which picks a floating screen by its
logical id among those in the current frame's map, and for it
Horizontal, Vertical and Depth Offset, Yaw, Pitch, Roll, Scale and
Visible, with Reset Screen and Reset All Screens. The rows apply live
and are hidden where they cannot apply, such as a tracked projection's
eyes. Stepping between screens reads one snapshot of the map and is
bounded, so a tracking change mid-step cannot leave it walking an empty
map.

docs/openxr.md describes the controls; the video_screens sample drives
them through the shipping menu actions.

* video: apply per-screen headset geometry to the frame snapshots

Each XR planner, Vulkan's and GL's, takes the normalized screen settings
with the frame snapshot, so a screen moves, turns, scales or hides as
its rows change, while paused too; recentering and tracked projections
are unaffected. Screen images are kept between frames, so shader
feedback survives a placement change.

A retained XR target that cannot be allocated is skipped quietly,
remembered by its size so it is not retried every frame; only the
desktop UI and canvas targets log the failure.

Covered by openxr_core_test, the video_screens sample, the e2e runner's
feedback shader case and the new vulkan_views_target sample.

* android: keep the OpenXR EGL context C89

gl_xr_egl_init declared its extension string after statements.

* video: pose inverse, layout spots and grab maths for headset edit mode

Each item's layout spot comes from one helper that placement uses and
the quad set records per slot, so a shown pose turns back into the
settings that placed it. Grab maths: the item follows the aim, level
unless tilting, offsets clamped to the settings' range; the stick pushes
along the ray and resizes. Edit picking sees every item and takes the
menu by a handle bar under it.

* video: a frame without a view map is screen 1 in the headset

A core without a views map (SNES, say) had no screen to select, so its
frame could not be moved. Its whole frame now takes screen 1's offsets,
turn, scale and visibility, and the selector lists Screen 1. gl's window
stays as laid out while the menu is open over it.

* video: the headset's menu in front of screen 1, or where it was put

The menu sat at a fixed spot, so moving screen 1 closer left the menu
behind it. It now floats 10 cm in front of screen 1, facing as it does,
at its default angular size and never nearer than 0.4 m, while screen 1
floats and shows, and is still submitted after the screens. A menu place
(video_openxr_menu_xr_* with video_openxr_menu_placed) overrides that;
it is saved like the per-screen keys, and Reset All Screens clears it.

* input: Edit Headset Screens mode for the OpenXR controllers

Edit Screens starts from a hotkey, a headset action, the
HEADSET_EDIT_SCREENS command or a Stereo 3D & Screens row, and stops on
the same, on opening the menu, on a video reinit or when the session
ends. It syncs its own action set in place of the game's, points the
lasers at every item whatever the Laser Pointer setting, keeps the
menu's quad up while editing, and with While Editing Screens set to
Pause pauses only content that was running. A runtime that refuses its
bindings keeps the game's and leaves edit mode unavailable.

The setting reaches the headset's poll with the others, and the pause
and resume are asked of the frontend as platform requests, so the input
driver issues no command and takes no settings itself. A Laser Pointer
value past its choices, which a hand-edited config can hold, reads as
Auto rather than as edit mode's.

* input: grab, tilt, push and resize headset screens in edit mode

A grip on an item takes it in the screens' frame: it keeps its pose
relative to the hand, stays level unless the trigger is held, and the
stick pushes it along the laser and resizes it, past a deadzone. Each
poll writes the item's offsets, turn and scale where the menu's rows
write them, through named frontend calls, clamped so it waits at a limit
and resumes from there. The menu is taken by its handle; a menu that
follows screen 1 is placed as large as it shows. The main button resets
a free item as Reset Screen does, or puts the menu back in front of
screen 1. A recenter, a screen leaving the map and leaving edit mode let
go, counted for the frame snapshot; an untracked hand holds its item
still.

* openxr: held screens follow the hand at the headset's rate

The main thread publishes what each hand holds through a seqlock; each
headset frame re-places a held item, both eyes of a stereo screen, from
that frame's own aim, so it no longer waits for the next video frame. An
item let go stays where the hand left it until a quad set planned at or
after its release (an edit count carried in the frame snapshot) shows
it, even once the hand holds another item, so nothing steps back. Lazy
Follow holds its glide while anything is held.

Edit mode outlines every item, brightly the one under a laser, and draws
the menu's handle bar under it, from the laser's image, now an atlas of
the dot, two outlines and two handle bars. They take only the layers the
screens leave after the dots.

* openxr: edit mode end-to-end cases and documentation

The test layer's aim can roll, and the e2e runner drives edit mode
against Monado: grabs that follow the hand on every headset frame and
stay where they are let go, level and tilt, the stick, resets of a
screen and of the menu, the menu taken by its handle and following
Screen 1 at its size, outlines and the handle, a no-map core, a views
core's fallback frame, Follow Head, Lazy Follow, recentering, two hands,
pausing, and what starts and ends edit mode, including the headset's
hotkey bits with no command interface. The cases whose work is a host's
or driver's own run on glcore and gl too. docs/openxr.md explains
editing screens in the headset and where the menu goes.

* openxr: a laser beam from each controller to its dot

A thin strip runs from each hand's aim to its dot, or 1.5 m into
nothing, turned about its own axis each headset frame to face the head
so it never shows edge on. It comes from the laser's image, so it needs
no new swapchain, and uses layers left after the dots and edit mode's
outlines, so it is the first to go when the runtime has few. The new
Laser Beam setting shows it in edit mode only (the default), always, or
never; outside edit mode, Laser Pointer and its hotkey hide it with the
dot.

* openxr: edit mode outlines in three bands, outside the item

A light band between two dark ones shows on white, black and busy
borders alike. The rim now sits wholly outside its item, 5 mm clear of
it even after filtering, so it never covers the picture; its region
grows to keep two texels a band at about the old thickness. Screens'
outlines are submitted before the screens, so a neighbour placed flush
covers the part of a rim over its picture, while the menu's outline and
handle bar stay on top. The handle bar moves 5 mm below the menu's
outline, which it would otherwise cover, and gets the same bands, faint
and bright.

* input: choose a headset edit item with Select Next and Previous

Edit mode steps from the laser through the screens it can move and the
menu. A chosen item is the one lit, either hand's grip takes it from
wherever the hand is, and Reset resets it, so a screen hidden behind
another can be moved. Whatever a hand holds stays lit.

A ray on the menu's handle bar takes the menu before any screen in front
of it. Within 3 cm of the bar, a ray that came from the bar keeps the
menu even over a screen, and one that came from elsewhere takes it only
where no screen is hit.

* openxr: snap a held headset screen to the screens near it

Snap to Screens, on by default, turns a held screen parallel or square
to a nearby screen and lines up its plane, edges or centre. Each pull is
exact within 3 cm or 5 degrees and fades out over as far again, so the
screen catches up with the hand rather than jumping, and snapping fades
in over the first 5 cm or 10 degrees after a grab, so taking a screen
never moves it. With several screens near, the snap that leaves the
least error wins, so a near screen's exact pull beats a far one's fading
pull whatever their order.

One stateless function inside the grab serves the main thread and the
headset's thread, so a screen is saved where it was shown. The holding
hand buzzes as a snap takes hold, once, and not again until the screen
has left that pull.

* input: turn a held headset item with the free hand's stick

A quarter turn of a held screen needed the controller turned a quarter
turn, which swung the screen round the hand on its long reach, so a
square snap could not be reached. Now the stick of the hand holding
nothing turns what the other hand holds about the vertical through its
middle, faster the further it is pushed, and snapping still catches it
parallel or square.

A carried item keeps its pitch and roll and its angle to the way the
hand points across the floor, so raising, lowering or twisting the hand
no longer tips or swivels it; the trigger lets it ride the hand rigidly.

* openxr: smooth the controller aim in edit mode

A screen held about 2 m out on the laser magnified every tremor of the
hand. In edit mode each hand's aim now goes through a One Euro filter,
which smooths a slow hand hard and a fast one little, with its rate
taken from the headset frame's own time step. The headset's thread
filters once a frame and hands the result to the main thread, which
grabs, moves and lets go from it, so a screen is saved where it was
shown. The new Edit Smoothing setting picks Off, Low, Medium (the
default) or High; outside edit mode the lasers are never smoothed.

* openxr: name the chosen edit item beside the controller

Edit mode now shows a faint tag just above the hand that last pressed
Select Next or Previous, reading Laser, Screen N or Menu, so what the
grip will take is plain without a notice. The words are drawn once into
the laser's image in the default font and the interface language, light
over a dark halo like the outlines so they read on any background, and
the tag is one more headset layer; when the runtime is short of layers
the beams are dropped before it.

On a runtime whose swapchains are too narrow for the words, the laser's
image keeps its old width and there is no tag.

* input: tilt a held headset item with the free hand's stick

In edit mode the free hand's stick already turns the item the other hand
holds. Its up and down now tilt that item too, about the item's own
left-right line through its middle, at the turn's rate and with no
limit: up tips the top back, as when pushing a monitor's top back. It
works whether the item is carried or rides the hand with the trigger
held, and snaps as the turn does.

Within about 25 degrees of left or right the stick only turns, and
within about 25 degrees of up or down it only tilts, so a push meant for
one does not drift into the other. Edit mode's starting notice lists the
turn and tilt and stays about 8 seconds.

* input: snap a turned or tilted headset item to angle steps

While the free hand's stick turns or tilts a held item in edit mode, the
item clicks into steps of the new Snap to Angles setting (15 degrees by
default; 30, 45, 90 or Off), so it is easy to set straight. The turn
counts from facing the user's head across the floor, the tilt from
upright. Within about 2 degrees of a step the item holds there and the
holding hand buzzes once; the stick's own angle keeps adding up, so
pushing on leaves the step.

Only the stick steps: carrying an item or tilting it with the trigger
never clicks, and a snap to another screen that would engage wins over a
step.

* openxr: show the back of a headset screen turned away

Runtimes draw only the front of a quad layer, so a screen or the menu
turned away from the head vanished. When the head is behind a quad's
plane, the frame now submits a plain dark panel in that quad's place: a
solid grey cell of the laser's image, at the quad's size, turned to
face the head. It takes the quad's own layer, so the layer count and
cap are unchanged. Edit mode picks either face, so an item turned away
stays grabbable, and its outline, handle and dot turn with its back.

* input: keep a screen snap's angle once the free stick lets go

A snap to another screen shows a held screen square or parallel to it,
but the item's own angle stays where the stick or the hand took it, up
to several degrees off. The next turn with the free hand's stick started
from that hidden angle, so the screen left the snap early on one side,
late on the other, and caught up with a lurch.

Now, as the free stick returns to its deadzone and as a push starts, a
snap engaged on the turn becomes the item's own angle: the carried turn,
or the orientation the trigger tilts. A snap still fading in never does,
since that would hop the screen into it. The place is left to the hand,
and carrying alone never keeps the angle, so pointing still pulls a
screen out of a snap. Letting go of the grip leaves the item where it
shows, as before.

* openxr: hinge a held headset screen to a neighbour's edge

Snap to Screens now also hinges a held screen to another along a
shared edge at any angle between them, so a DS or 3DS pair can be
opened at a slant: an edge within 5 degrees of parallel to one of the
other's, within 3 cm of its line and sharing some length with it,
turns exactly parallel and moves onto it, centres or ends level, its
angle left free. The pull fades in over the first 3 cm of shared
length, so a screen slides off the end of the other's edge without a
jump, and a diagonal neighbour never hinges. Screens that would
overlap do not hinge; the pull is whole from flat to square and fades
out folded shut.

Flat and square snap as before and report a hinge at 0 and 90. A turn
to the same screen pivots on the shared edge, so the edge stays on the
line as the screen leaves flat or square. The snap still keeps nothing
between calls; its result carries a hinge bit, and video_xr_snap_hinge
also says which edge and line, for the stick to swing about.

* input: swing a hinged headset screen about the shared edge

While a held screen is hinged to another, the free hand's stick axis
that turns nearest the shared edge, the tilt for a top or bottom edge
and the turn for a side one, now swings it about that edge, so it stays
attached; the other axis still turns or tilts it and can pull it off.
Where the swing leaves it, angle and place, it stays as the stick lets
go. Snap to Angles counts a hinged screen's steps from flat with the
other screen.

A hinge counts only while the screen's own edge is within its range,
not when other snaps alone hold it on the line, so a swing never
carries a screen round a line it is far from.

A square corner is a side hinge at 90 degrees, so the turn stick there
now swings the screen about the corner's edge. Main thread only;
nothing new reaches the frame loop, which still shows the grab through
video_xr_grab_pose.

* openxr: leave a gap between hinged headset screens

Edges that meet in edit mode, side by side, at a corner or hinged at any
angle, now click to a set gap from within 1 cm and otherwise keep the
gap the hand gives them, still hinged, up to 10 cm (letting go by 13).
Screens no longer have to touch, which made them overlap. Each grab
carries the gap, so the edit poll and the frame loop snap alike; until a
setting sets it, it is 0.

Pushed past touching, a hinged edge stops there for the first centimetre
of the push, the stop fading out by 3 cm, and ticks once per push.

A swing pivots on the held screen's own edge, so the gap keeps through
it, and a commit keeps the hand's gap unless the click holds it. After a
swing lets go past square, that stick axis and its steps keep pivoting
on the same edge until the edge leaves the hinge's reach, so a flick
past square and back does not creep.

Of two neighbours within the 5 degree turn snap, the nearer edge wins
the turn and the hinge, by one measure for every screen, so a screen
stacked behind, one off the ends of the edge or a side wall at a corner
never outranks a hinge the turn holds.

* menu: add Hinge Gap for headset screens

Hinge Gap (0, 1, 2, 3, 5 or 10 cm, default 2) sets the gap edit mode's
Snap to Screens leaves between screens that meet edge to edge, side by
side, at a corner or hinged. Edit mode takes it as it starts and gives
it to each grab; the click to it buzzes like the other snaps.

* menu: put Edit Screens under Headset Output

Stereo 3D & Screens now lists Edit Screens and its snaps right under the
switch that reveals them, then the headset's own setup, then where the
screens go, with the per-screen rows last and Visible first among them.

* openxr: track each held screen's hinge on the frame loop

The frame loop already re-snaps each held screen every frame. It now
also keeps, per hand, the hinge that snap holds whole, until the edge
leaves the hinge's 4.5 cm reach, where the frame draws it, so edit mode
can mark it. A kept hinge whose target no longer matches is dropped.

video_xr gains the hinge line's ends, along the overlap of the two real
edges and midway across the gap, an outline cut that drops one edge's
rim, and a green strip in the laser's image, before the tags.

* openxr: mark a hinged headset screen with a green line

While a held screen's hinge holds, the two edges that meet drop their
outline rim and a 6 mm green line runs along the hinge where they
overlap, midway across the gap, so it is clear when the hinge has taken
hold.

The line is drawn as the beam is, a centimetre nearer the eye than the
edges, since SteamVR does not keep the order of overlapping quad layers,
and comes after the outlines in the layer budget; hidden rims cost no
layer.

* input: buzz longer as a headset screen hinges

A hinge taking hold now gets one 150 ms buzz at 0.8 on the hand carrying
the screen, against the 20 ms snap tick, so the attach can be felt. It
fires again only after the edge leaves the hinge's reach, hinges
elsewhere, or the other hand takes the screen it hinges on, so a swing
past square and back does not repeat it. Ticks due while it plays are
skipped rather than cutting it short.

* openxr: find headset screens resting in a hinge

Two screens no hand holds rest in a hinge when their poses pass the
test that engages a carried screen's hinge, with either one carried,
and the hinge, held whole with nothing else pulling, leaves the carried
one where it is. So a hinge found at rest stays hinged when its screen
is taken. The engaged test moves into its own function so both use the
same one, and one target can pull whole whatever the grab's fade.

* openxr: keep a headset screen's hinge shown at rest

In Edit Screens, two screens no hand holds that rest in a hinge keep
the green line and their hidden rims, whether let go hinged or loaded
that way. The main thread counts them only when a resting screen
changes and sends them with the hands' record, so a release and its
resting pair arrive together; the frame loop places each line from
the screens it shows. A screen hinged at rest on two adjacent edges
still keeps one of those rims.

The hands' record now outgrows the quads, so the seqlock's copy is
sized to the larger of the two.

* openxr: pick up a hinged headset screen still hinged

Taking up a screen that rests in a hinge starts the carry on that hinge,
whole from the first poll, with no buzz and no jump, until it leaves the
hinge's reach, another hinge takes hold, or the other hand takes the
screen it hinges on. Until the grab's own snapping has faded in, the
kept hinge's target still pulls whole, so a straight pull out does not
hop.

The grab carries the hinge so both threads hold it the same; of several,
the one the snap ranks best carries over, and only where holding it
moves nothing. While the other screens fade in, the hinge's screen also
leads the turn, so a nearer one still fading does not take it.

* openxr: end a headset screen's hinge where a push passes the stop

Pushing a hinged screen into its neighbour holds the edge at touching
for 3 cm of push, fading out by 5.5 cm. Where it stops holding, the
hinge ends: the line goes, the rims come back, and the lid and buzz are
cleared, so screens pushed into each other show no hinge that a let-go
there would not rest in. Pulled back, the hinge takes hold again 5 mm
short of where it ended, with the long buzz, so a tremor there does not
flicker it.

The grab carries that through flag, and the frame loop keeps its line
until the main thread sets it, so the two threads never disagree for
more than a poll.

At Hinge Gap 1 a pair let go touching rests hinged and is taken up
hinged, as at every other gap. A screen swung into a square corner gets
the long buzz on arrival, and the other hand taking the target does not
hop the carried screen by the push's depth.

* input: let go of a headset screen where it was before the grip opened

Opening the grip twitches the hand, and the poll that sees the grip
open would move the screen by that twitched aim. Half a degree of pitch
moves a screen 1.8 m out by 1.6 cm, enough to knock it off its hinge's
gap or out of level, so it would not rest hinged.

Each hand keeps a short trail of where, turned and as large as its
screen showed, entries at least 10 ms apart, and a grip let go puts the
screen as it showed 80 ms before (INPUT_OPENXR_EDIT_REWIND_US), or as it
was taken if held for less. Edit mode's end and a recenter let go where
the screen shows.

* openxr: find where a let-go headset screen settles in its hinge

A screen let go while its hinge shows should land exactly in that hinge,
so the hinge still shows at rest and a pick-up is hinged.
video_xr_hinge_settle holds the hinge whole from the hand's pose, then
moves the edge to the set gap from within 3 cm of it (touching stays
where the stop holds it) and levels it along the edge within the level
pull's reach. Its result must pass the rest test, and it turns no
further than a grab's hinge engages, which shares that test.

A pair resting inside a settle band but off its point is no longer
listed at rest, since a pick-up and let-go would move it. A touching
rest is taken 5 mm deeper into the push stop, at every Hinge Gap, so a
hand that drifts less than that apart before letting go settles touching
rather than at the gap.

* openxr: settle a let-go headset screen into its hinge

A screen let go while its hinge shows now goes exactly into that hinge:
to the set gap from within 3 cm of it, touching from the stop's hold,
and level along the edge from within the level pull's reach. It then
rests there, listed and green, in the same publish, and is saved
there. Elsewhere it stays where it shows, as before.

Each trail entry also records where the hand put the screen with no
snap and whether a let-go there settles. At the let-go the rewound
entry is settled again against the screens as they are now, at that
entry's size, and put in one go.

* openxr: let a hinged headset screen rest at any fold

Snap to Screens' turn to flat, and its turn to square at a corner, is
whole within 5 degrees and fades out by 10, so a hinged screen held
there shows at twice its angle off less 10. No fold shown in that fade
read as a rest: the pair was not listed, a let-go there did not settle
into its hinge, and taking it up would have moved it.

Where a hand takes a rest, it now holds the screen at the angle the
turn's fade shows it from, the inverse of the turn's pull, so every
fold rests, is listed and is taken up unmoved. Held snapping is
unchanged.

* input: keep a headset screen's hinge through a shaking hand

Edit Screens' hinge buzz, lid and push stop followed the snap's own
engage and reach on every poll. A hand shaking half a degree (1.6 cm at
the screen) about a hinge's edge flipped them many times a second, each
flip a long buzz.

Each hold now keeps one mark: the hinge a let-go would settle into. It
takes hold at once, with the long buzz, and lets go only once polls have
seen the hand out of where a let-go would settle for 150 ms, each poll
counting at most 50 ms, so a stalled main thread or a spell untracked
never lets it go alone. The other hand taking its target, hinged
elsewhere or not, the target leaving and the let-go end it at once. The
lid waits for the mark to go, the push stop follows it, and a let-go
while it shows settles into it from where the hand last would have, edit
mode's end and a recenter included.

* openxr: draw a held screen's hinge as the main thread marks it

The grab carries the main thread's hinge mark, and the frame loop draws
a held screen's green line and cuts its rims from that mark alone,
through video_xr_grab_marked, while it snaps the screen every frame. A
frame loop taking the hinge by itself between polls would flicker the
line for a hand shaking at the edge of the hinge's range while the mark
held. A mark whose target the frame does not show cuts no rim.

The other hand taking the mark's target ends it, and the lid, on any
poll, tracked or not, so a lost hand's line does not come back when that
hand lets go.

video_xr_grab_near_hinge loses its past argument, which only the frame
loop's keep used.

* openxr: keep edit mode's hinge layers in place

SteamVR does not keep submission order for overlapping quad layers, so
a layer added, dropped or moved to another index can pop the edit
outlines in front of the screens, as a wobble at a hinge would do to
its line's layer.

The hinge lines are fixed slots: one for each hand holding a
screen, hand 0 first, then one for each pair at rest. A slot with no
line to show is drawn in place from its screen's middle, at rows of
the laser image that are empty either way up. The layer count and
every index change only at a grab, a let-go or a re-count of the pairs
at rest; a mark only changes crops and which rows a slot shows.

* input: date a let-go's trail by frame and stop it at a stall

Each trail entry is dated by the frame its aims are from, one a frame
and per hand, and the release poll records a new frame's aims first.
The rewind stops at a stall, entries more than 2.5 frames apart, a
frame counted as at least 20 ms so a fast headset's close entries never
read as one. Under load a stall of 0.1 to 0.3 s can come just before
the release; the entry from 80 ms back would then hold the pose from
before it and step the let-go back across all the stall's motion.

With Edit Smoothing on, which damps the twitch, a let-go that does not
settle into a hinge stays where and as large as it shows: a rewind there
would step back by what the smoothed aim still lags. The hinge decision
and the settle use the trail either way.

* openxr: cut edit outlines from cells instead of turning them

Cutting a top or bottom rim from the one outline image turns both
outline quads a quarter turn and swaps their width and height, and on
SteamVR that can blink every edit outline as the hinge takes hold or
lets go; side cuts, which never turn, do not.

The laser's image carries twelve pre-cut outline cells after the
tags, faint and bright, for a cut top, bottom, left, right, top and
bottom, or left and right, so a cut only picks a cell and the quads keep
their pose and size. The image is then 5582 texels wide. A runtime whose
swapchains are narrower, or that does not say how wide they can be,
keeps the turned and cropped outline at 2498 or 1208 wide. One log line
at the laser's creation gives the width chosen and the runtime's limit.
GL writes the top and bottom cells swapped for its bottom-up rows, and
Android's copy of the image is allocated rather than static.

The edit-hinge-3ds-narrow e2e case runs the turned cut at
RA_XR_LAYER_MAX_WIDTH=4096, on Vulkan and the GL EGL lanes.

* video: draw headset screens straight into the runtime's images

Each headset screen was drawn into an image of the driver's own and
copied from it into the runtime's on every new frame, Vulkan with a
render pass and GL3 with a blit. Screens are now drawn into the
runtime's image itself, as before per-screen placement, with no image
of the driver's and no copy.

A screen drawn again with no new frame - resized, made again or moved
while paused - replays the last frame's final pass from the chain:
build_viewport_pass_replay draws it at the new size from what it read
then, the passes' outputs, their feedback and the history that frame
saw, with that frame's count. Nothing else runs, feedback does not
swap and history stays, so a feedback or history preset holds still
while paused. A chain made to replay keeps one more history image, so
the frame's end writes where the last frame did not read; the window's
chains keep what they did. This also has movie frames reach the
headset, which the copy held at the first one, and draws a screen
resized while paused at its new size rather than stretching the old
image.

* openxr: leave what users who never edit see as it was

Screens seen from behind turn to their grey backs only while Edit
Screens is on, where screens are turned around; outside it nothing
changes and no head is located for them. The grabs carry whether
editing is on.

The menu opens at its own spot, straight ahead of the anchor, as it
did. Menu in Front of Screen 1, off by default, keeps it just in front
of screen 1 and moving with it.

The frame loop copies the main thread's grabs only once their stamp
moves, so outside edit mode no frame copies them. A new session clears
the frame loop's own copy and passes over what was published before
it, rather than publishing itself: the main thread stays the grabs'
one writer.

On Touch, the left menu button that opens the menu from edit mode is
spent until let go: held on as edit mode ends, it is no Start and does
not toggle the menu again after a second.

---------

Co-authored-by: libretroadmin <[email protected]>
This commit is contained in:
Davey Hughesandlibretroadmin authored and GitHub committed 2026-10-30 05:40:25 +01:00
1 parent 770391e8d5
commit 6ebf66b643
74 files changed
+33449 -524

No files matched your search

+45 -1
View File
@@ -913,13 +913,31 @@ jobs:
# Vulkan through the symbol wrapper pointed at mocks, with
# each allocation it makes failed in turn: a chain comes back
# with every pass and every deferred-disposal queue, or not
# at all, and nothing is left behind.
# at all, and nothing is left behind. A replay of the last
# frame's final pass reads what that pass read, and a chain
# made to replay keeps one history image more.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
timeout 60 ./vulkan_chain_alloc_test
make clean
echo "[pass] vulkan_chain_alloc_test"
- name: Build and run gl3_shader_status_test and gl3_chain_alloc_test (ASan + UBSan)
shell: bash
working-directory: samples/gfx/gl3_shader_status
run: |
set -eu
# gfx/drivers_shader/shader_gl3.c with GL through the glsym
# table pointed at mocks: shader status checks, the chain's
# allocations failed in turn, and a replay of the last frame's
# final pass reading the textures, at the generations, that
# pass read, the history keeping a spare.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
timeout 60 make check SANITIZER=address,undefined
make clean
echo "[pass] gl3_shader_status_test, gl3_chain_alloc_test"
- name: Build and run vulkan_texture_size_test (ASan)
shell: bash
working-directory: samples/gfx/vulkan_texture_size
@@ -947,6 +965,32 @@ jobs:
timeout 60 ./vulkan_texture_size_test
echo "[pass] vulkan_texture_size_test"
- name: Build and run vulkan_views_target_test
shell: bash
working-directory: samples/gfx/vulkan_views_target
run: |
set -eu
# gfx/drivers/vulkan.c's retained view targets and headset
# draw, copied out of the driver, against a mocked device: a
# failed allocation is not retried at the same size, a resize
# waits while both retired slots are full, and each headset
# screen is drawn straight into the runtime's image, a repeat
# ending no chain frame.
make clean && timeout 60 make check
echo "[pass] vulkan_views_target_test"
- name: Build and run gl3_xr_draw_test
shell: bash
working-directory: samples/gfx/gl3_xr_draw
run: |
set -eu
# gfx/drivers/gl3.c's headset draw, copied out of the driver,
# with GL stubbed: each screen straight into the runtime's
# texture, and a screen moved or resized while paused drawn
# again without ending a chain frame.
make clean && timeout 60 make check
echo "[pass] gl3_xr_draw_test"
- name: GLES persistent loan pixel oracle (Mesa EGL)
shell: bash
run: |
+13 -1
View File
@@ -28,6 +28,8 @@ on:
- 'gfx/video_views_draw.h'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'configuration.*'
- 'samples/settings/video_screens/**'
- 'samples/cores/video_views/**'
- '.github/workflows/Windows-wine-views.yml'
pull_request:
@@ -45,6 +47,8 @@ on:
- 'gfx/video_views_draw.h'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'configuration.*'
- 'samples/settings/video_screens/**'
- 'samples/cores/video_views/**'
- '.github/workflows/Windows-wine-views.yml'
workflow_dispatch:
@@ -136,10 +140,11 @@ jobs:
set -eu
export CCACHE_DIR="$HOME/.ccache"
ccache -M 250M
# The screens' windows too, which video_screens_test runs.
./configure --host=x86_64-w64-mingw32 \
--disable-qt --disable-vulkan --disable-ffmpeg \
--disable-cheevos --disable-discord --enable-d3d8 \
--disable-sdl --disable-sdl2
--disable-sdl --disable-sdl2 --enable-video_windows
make -j$(nproc) CC="ccache x86_64-w64-mingw32-gcc" \
CXX="ccache x86_64-w64-mingw32-g++"
ccache -s
@@ -149,6 +154,13 @@ jobs:
done
make -C samples/cores/video_views DLL=1 CC=x86_64-w64-mingw32-gcc
- name: Build and run video_screens_test under Wine
shell: bash
run: |
set -eu
WINEDEBUG=-all xvfb-run -a python3 samples/settings/video_screens/build.py \
--output-dir "$RUNNER_TEMP/video-screens" --runner wine
- name: Views on GDI, D3D8, D3D9, D3D10, D3D11 and D3D12, end to end (Wine, Xvfb, llvmpipe, lavapipe)
shell: bash
run: |
+3
View File
@@ -286,6 +286,8 @@ enum event_command
CMD_EVENT_LASER_POINTER_TOGGLE,
/* Cycle what a core is told the headset displays: immersive, stereoscopic, flat. */
CMD_EVENT_HEADSET_VIEW_TOGGLE,
/* Start or stop moving the headset's screens with its controllers. */
CMD_EVENT_HEADSET_EDIT_SCREENS,
/* AI service. */
CMD_EVENT_AI_SERVICE_TOGGLE,
CMD_EVENT_AI_SERVICE_CALL,
@@ -1367,6 +1369,7 @@ static const struct cmd_map map[] = {
{ "HEADSET_RECENTER", RARCH_HEADSET_RECENTER, "Recenter the headset's screens in front of where it looks.", CMD_INFO_NO_TOOL },
{ "LASER_POINTER_TOGGLE", RARCH_LASER_POINTER_TOGGLE, "Show or hide the headset's laser pointer.", CMD_INFO_NO_TOOL },
{ "HEADSET_VIEW_TOGGLE", RARCH_HEADSET_VIEW_TOGGLE, "Cycle the headset view between immersive, stereoscopic and flat.", CMD_INFO_NO_TOOL },
{ "HEADSET_EDIT_SCREENS", RARCH_HEADSET_EDIT_SCREENS, "Start or stop moving the headset's screens and menu with its controllers.", CMD_INFO_NO_TOOL },
{ "FPS_TOGGLE", RARCH_FPS_TOGGLE, "Show or hide the framerate.", CMD_INFO_NO_TOOL },
{ "STATISTICS_TOGGLE", RARCH_STATISTICS_TOGGLE, "Show or hide the technical statistics.", CMD_INFO_NO_TOOL },
{ "AI_SERVICE", RARCH_AI_SERVICE, "Run the AI service (translate or narrate the screen).", CMD_INFO_NO_TOOL },
+20
View File
@@ -469,6 +469,11 @@ static const struct retro_keybind_def retro_keybinds_1[] = {
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_VIEW_TOGGLE, RETROK_UNKNOWN, true),
RARCH_HEADSET_VIEW_TOGGLE, NO_BTN, NO_BTN, 0
},
{
AXIS_NONE, AXIS_NONE,
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_EDIT_SCREENS, RETROK_UNKNOWN, true),
RARCH_HEADSET_EDIT_SCREENS, NO_BTN, NO_BTN, 0
},
{
AXIS_NONE, AXIS_NONE,
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE, RETROK_UNKNOWN, true),
@@ -966,6 +971,11 @@ static const struct retro_keybind_def retro_keybinds_1[] = {
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_VIEW_TOGGLE, RETROK_UNKNOWN, true),
RARCH_HEADSET_VIEW_TOGGLE, NO_BTN, NO_BTN, 0
},
{
AXIS_NONE, AXIS_NONE,
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_EDIT_SCREENS, RETROK_UNKNOWN, true),
RARCH_HEADSET_EDIT_SCREENS, NO_BTN, NO_BTN, 0
},
{
AXIS_NONE, AXIS_NONE,
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE, RETROK_UNKNOWN, true),
@@ -1471,6 +1481,11 @@ static const struct retro_keybind_def retro_keybinds_1[] = {
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_VIEW_TOGGLE, RETROK_v, true),
RARCH_HEADSET_VIEW_TOGGLE, NO_BTN, NO_BTN, 0
},
{
AXIS_NONE, AXIS_NONE,
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_EDIT_SCREENS, RETROK_UNKNOWN, true),
RARCH_HEADSET_EDIT_SCREENS, NO_BTN, NO_BTN, 0
},
{
AXIS_NONE, AXIS_NONE,
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE, RETROK_F3, true),
@@ -1965,6 +1980,11 @@ static const struct retro_keybind_def retro_keybinds_rest[] = {
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_VIEW_TOGGLE, RETROK_UNKNOWN, true),
RARCH_HEADSET_VIEW_TOGGLE, NO_BTN, NO_BTN, 0
},
{
AXIS_NONE, AXIS_NONE,
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_EDIT_SCREENS, RETROK_UNKNOWN, true),
RARCH_HEADSET_EDIT_SCREENS, NO_BTN, NO_BTN, 0
},
{
AXIS_NONE, AXIS_NONE,
RETRO_KEYBIND_PACK(MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE, RETROK_UNKNOWN, true),
+361
View File
@@ -39,6 +39,7 @@
#include "file_path_special.h"
#include "command.h"
#include "configuration.h"
#include "gfx/video_views.h"
#include "gfx/video_windows.h"
#include "content.h"
#include "config.def.h"
@@ -455,6 +456,7 @@ const struct input_bind_map input_config_bind_map[RARCH_BIND_LIST_END_NULL] = {
DECLARE_META_BIND(2, headset_recenter, RARCH_HEADSET_RECENTER, MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_RECENTER),
DECLARE_META_BIND(2, laser_pointer_toggle, RARCH_LASER_POINTER_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_LASER_POINTER_TOGGLE),
DECLARE_META_BIND(2, headset_view_toggle, RARCH_HEADSET_VIEW_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_VIEW_TOGGLE),
DECLARE_META_BIND(2, headset_edit_screens, RARCH_HEADSET_EDIT_SCREENS, MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_EDIT_SCREENS),
DECLARE_META_BIND(2, fps_toggle, RARCH_FPS_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE),
DECLARE_META_BIND(2, toggle_statistics, RARCH_STATISTICS_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_STATISTICS_TOGGLE),
DECLARE_META_BIND(2, ai_service, RARCH_AI_SERVICE, MENU_ENUM_LABEL_VALUE_INPUT_META_AI_SERVICE),
@@ -5889,6 +5891,127 @@ static struct config_int_setting *populate_settings_int(
strlcpy(playlist_path, new_file, sizeof(playlist_path)); \
} \
void config_reset_video_screen_xr(settings_t *settings,
unsigned first, unsigned count)
{
video_screen_xr_t screen;
unsigned i;
if (!settings || first >= RETRO_VIDEO_VIEWS_MAX)
return;
if (count > RETRO_VIDEO_VIEWS_MAX - first)
count = RETRO_VIDEO_VIEWS_MAX - first;
video_views_xr_reset(&screen, 1);
for (i = first; i < first + count; i++)
{
settings->floats.video_screen_xr_x[i] = screen.x;
settings->floats.video_screen_xr_y[i] = screen.y;
settings->floats.video_screen_xr_z[i] = screen.z;
settings->floats.video_screen_xr_yaw[i] = screen.yaw;
settings->floats.video_screen_xr_pitch[i] = screen.pitch;
settings->floats.video_screen_xr_roll[i] = screen.roll;
settings->floats.video_screen_xr_scale[i] = screen.scale;
settings->bools.video_screen_xr_visible[i] = screen.visible;
}
}
void config_reset_video_menu_xr(settings_t *settings)
{
video_screen_xr_t menu;
if (!settings)
return;
video_views_xr_reset(&menu, 1);
settings->floats.video_openxr_menu_xr_x = menu.x;
settings->floats.video_openxr_menu_xr_y = menu.y;
settings->floats.video_openxr_menu_xr_z = menu.z;
settings->floats.video_openxr_menu_xr_yaw = menu.yaw;
settings->floats.video_openxr_menu_xr_pitch = menu.pitch;
settings->floats.video_openxr_menu_xr_roll = menu.roll;
settings->floats.video_openxr_menu_xr_scale = menu.scale;
settings->bools.video_openxr_menu_placed = false;
}
void config_get_video_screen_xr(unsigned screen, video_screen_xr_t *out)
{
settings_t *settings = config_get_ptr();
video_views_xr_reset(out, 1);
if (!settings || screen >= RETRO_VIDEO_VIEWS_MAX)
return;
out->x = settings->floats.video_screen_xr_x[screen];
out->y = settings->floats.video_screen_xr_y[screen];
out->z = settings->floats.video_screen_xr_z[screen];
out->yaw = settings->floats.video_screen_xr_yaw[screen];
out->pitch = settings->floats.video_screen_xr_pitch[screen];
out->roll = settings->floats.video_screen_xr_roll[screen];
out->scale = settings->floats.video_screen_xr_scale[screen];
out->visible = settings->bools.video_screen_xr_visible[screen];
}
bool config_get_video_menu_xr(video_screen_xr_t *out)
{
settings_t *settings = config_get_ptr();
video_views_xr_reset(out, 1);
if (!settings)
return false;
out->x = settings->floats.video_openxr_menu_xr_x;
out->y = settings->floats.video_openxr_menu_xr_y;
out->z = settings->floats.video_openxr_menu_xr_z;
out->yaw = settings->floats.video_openxr_menu_xr_yaw;
out->pitch = settings->floats.video_openxr_menu_xr_pitch;
out->roll = settings->floats.video_openxr_menu_xr_roll;
out->scale = settings->floats.video_openxr_menu_xr_scale;
return settings->bools.video_openxr_menu_placed;
}
void config_set_video_screen_xr(unsigned screen,
const video_screen_xr_t *values)
{
video_screen_xr_t s;
settings_t *settings = config_get_ptr();
if (!settings || screen >= RETRO_VIDEO_VIEWS_MAX)
return;
if (!values)
config_reset_video_screen_xr(settings, screen, 1);
else
{
s = *values;
video_views_xr_normalize(&s);
settings->floats.video_screen_xr_x[screen] = s.x;
settings->floats.video_screen_xr_y[screen] = s.y;
settings->floats.video_screen_xr_z[screen] = s.z;
settings->floats.video_screen_xr_yaw[screen] = s.yaw;
settings->floats.video_screen_xr_pitch[screen] = s.pitch;
settings->floats.video_screen_xr_roll[screen] = s.roll;
settings->floats.video_screen_xr_scale[screen] = s.scale;
}
settings->flags |= SETTINGS_FLG_MODIFIED;
}
void config_set_video_menu_xr(const video_screen_xr_t *values)
{
video_screen_xr_t s;
settings_t *settings = config_get_ptr();
if (!settings)
return;
if (!values)
config_reset_video_menu_xr(settings);
else
{
s = *values;
video_views_xr_normalize(&s);
settings->floats.video_openxr_menu_xr_x = s.x;
settings->floats.video_openxr_menu_xr_y = s.y;
settings->floats.video_openxr_menu_xr_z = s.z;
settings->floats.video_openxr_menu_xr_yaw = s.yaw;
settings->floats.video_openxr_menu_xr_pitch = s.pitch;
settings->floats.video_openxr_menu_xr_roll = s.roll;
settings->floats.video_openxr_menu_xr_scale = s.scale;
settings->bools.video_openxr_menu_placed = true;
}
settings->flags |= SETTINGS_FLG_MODIFIED;
}
/**
* config_set_defaults:
*
@@ -6243,6 +6366,9 @@ void config_set_defaults(settings_t *target)
settings->uints.input_touchpad_right[i] = RETRO_DEVICE_ID_JOYPAD_START;
}
config_reset_video_screen_xr(settings, 0, RETRO_VIDEO_VIEWS_MAX);
config_reset_video_menu_xr(settings);
#ifdef HAVE_VIDEO_WINDOWS
for (i = 0; i < RETRO_VIDEO_VIEWS_MAX; i++)
{
@@ -7310,6 +7436,63 @@ static bool config_load_file(const char *path, settings_t *settings)
config_load_video_windows(settings, conf);
#endif
for (i = 0; i < RETRO_VIDEO_VIEWS_MAX; i++)
{
video_screen_xr_t screen;
char prefix[64];
size_t len = snprintf(prefix, sizeof(prefix), "video_screen%u_xr_", i + 1);
video_views_xr_reset(&screen, 1);
strlcpy_lit(prefix + len, "x", sizeof(prefix) - len);
config_get_float(conf, prefix, &screen.x);
strlcpy_lit(prefix + len, "y", sizeof(prefix) - len);
config_get_float(conf, prefix, &screen.y);
strlcpy_lit(prefix + len, "z", sizeof(prefix) - len);
config_get_float(conf, prefix, &screen.z);
strlcpy_lit(prefix + len, "yaw", sizeof(prefix) - len);
config_get_float(conf, prefix, &screen.yaw);
strlcpy_lit(prefix + len, "pitch", sizeof(prefix) - len);
config_get_float(conf, prefix, &screen.pitch);
strlcpy_lit(prefix + len, "roll", sizeof(prefix) - len);
config_get_float(conf, prefix, &screen.roll);
strlcpy_lit(prefix + len, "scale", sizeof(prefix) - len);
config_get_float(conf, prefix, &screen.scale);
strlcpy_lit(prefix + len, "visible", sizeof(prefix) - len);
config_get_bool(conf, prefix, &screen.visible);
video_views_xr_normalize(&screen);
settings->floats.video_screen_xr_x[i] = screen.x;
settings->floats.video_screen_xr_y[i] = screen.y;
settings->floats.video_screen_xr_z[i] = screen.z;
settings->floats.video_screen_xr_yaw[i] = screen.yaw;
settings->floats.video_screen_xr_pitch[i] = screen.pitch;
settings->floats.video_screen_xr_roll[i] = screen.roll;
settings->floats.video_screen_xr_scale[i] = screen.scale;
settings->bools.video_screen_xr_visible[i] = screen.visible;
}
{
video_screen_xr_t menu;
bool placed = false;
video_views_xr_reset(&menu, 1);
config_get_float(conf, "video_openxr_menu_xr_x", &menu.x);
config_get_float(conf, "video_openxr_menu_xr_y", &menu.y);
config_get_float(conf, "video_openxr_menu_xr_z", &menu.z);
config_get_float(conf, "video_openxr_menu_xr_yaw", &menu.yaw);
config_get_float(conf, "video_openxr_menu_xr_pitch", &menu.pitch);
config_get_float(conf, "video_openxr_menu_xr_roll", &menu.roll);
config_get_float(conf, "video_openxr_menu_xr_scale", &menu.scale);
config_get_bool(conf, "video_openxr_menu_placed", &placed);
video_views_xr_normalize(&menu);
settings->floats.video_openxr_menu_xr_x = menu.x;
settings->floats.video_openxr_menu_xr_y = menu.y;
settings->floats.video_openxr_menu_xr_z = menu.z;
settings->floats.video_openxr_menu_xr_yaw = menu.yaw;
settings->floats.video_openxr_menu_xr_pitch = menu.pitch;
settings->floats.video_openxr_menu_xr_roll = menu.roll;
settings->floats.video_openxr_menu_xr_scale = menu.scale;
settings->bools.video_openxr_menu_placed = placed;
}
{
char prefix[64];
size_t _len = strlcpy_lit(prefix, "input_player", sizeof(prefix));
@@ -9816,6 +9999,111 @@ bool config_save_file(const char *path)
}
#endif
for (i = 0; i < RETRO_VIDEO_VIEWS_MAX; i++)
{
char cfg[64];
size_t len = snprintf(cfg, sizeof(cfg), "video_screen%u_xr_", i + 1);
strlcpy_lit(cfg + len, "x", sizeof(cfg) - len);
if ( !minimal
|| settings->floats.video_screen_xr_x[i] != defaults->floats.video_screen_xr_x[i])
config_set_float(conf, cfg, settings->floats.video_screen_xr_x[i]);
else
config_unset(conf, cfg);
strlcpy_lit(cfg + len, "y", sizeof(cfg) - len);
if ( !minimal
|| settings->floats.video_screen_xr_y[i] != defaults->floats.video_screen_xr_y[i])
config_set_float(conf, cfg, settings->floats.video_screen_xr_y[i]);
else
config_unset(conf, cfg);
strlcpy_lit(cfg + len, "z", sizeof(cfg) - len);
if ( !minimal
|| settings->floats.video_screen_xr_z[i] != defaults->floats.video_screen_xr_z[i])
config_set_float(conf, cfg, settings->floats.video_screen_xr_z[i]);
else
config_unset(conf, cfg);
strlcpy_lit(cfg + len, "yaw", sizeof(cfg) - len);
if ( !minimal
|| settings->floats.video_screen_xr_yaw[i] != defaults->floats.video_screen_xr_yaw[i])
config_set_float(conf, cfg, settings->floats.video_screen_xr_yaw[i]);
else
config_unset(conf, cfg);
strlcpy_lit(cfg + len, "pitch", sizeof(cfg) - len);
if ( !minimal
|| settings->floats.video_screen_xr_pitch[i] != defaults->floats.video_screen_xr_pitch[i])
config_set_float(conf, cfg, settings->floats.video_screen_xr_pitch[i]);
else
config_unset(conf, cfg);
strlcpy_lit(cfg + len, "roll", sizeof(cfg) - len);
if ( !minimal
|| settings->floats.video_screen_xr_roll[i] != defaults->floats.video_screen_xr_roll[i])
config_set_float(conf, cfg, settings->floats.video_screen_xr_roll[i]);
else
config_unset(conf, cfg);
strlcpy_lit(cfg + len, "scale", sizeof(cfg) - len);
if ( !minimal
|| settings->floats.video_screen_xr_scale[i] != defaults->floats.video_screen_xr_scale[i])
config_set_float(conf, cfg, settings->floats.video_screen_xr_scale[i]);
else
config_unset(conf, cfg);
strlcpy_lit(cfg + len, "visible", sizeof(cfg) - len);
if ( !minimal
|| settings->bools.video_screen_xr_visible[i] != defaults->bools.video_screen_xr_visible[i])
config_set_string(conf, cfg,
settings->bools.video_screen_xr_visible[i] ? "true" : "false");
else
config_unset(conf, cfg);
}
if ( !minimal
|| settings->floats.video_openxr_menu_xr_x != defaults->floats.video_openxr_menu_xr_x)
config_set_float(conf, "video_openxr_menu_xr_x", settings->floats.video_openxr_menu_xr_x);
else
config_unset(conf, "video_openxr_menu_xr_x");
if ( !minimal
|| settings->floats.video_openxr_menu_xr_y != defaults->floats.video_openxr_menu_xr_y)
config_set_float(conf, "video_openxr_menu_xr_y", settings->floats.video_openxr_menu_xr_y);
else
config_unset(conf, "video_openxr_menu_xr_y");
if ( !minimal
|| settings->floats.video_openxr_menu_xr_z != defaults->floats.video_openxr_menu_xr_z)
config_set_float(conf, "video_openxr_menu_xr_z", settings->floats.video_openxr_menu_xr_z);
else
config_unset(conf, "video_openxr_menu_xr_z");
if ( !minimal
|| settings->floats.video_openxr_menu_xr_yaw != defaults->floats.video_openxr_menu_xr_yaw)
config_set_float(conf, "video_openxr_menu_xr_yaw", settings->floats.video_openxr_menu_xr_yaw);
else
config_unset(conf, "video_openxr_menu_xr_yaw");
if ( !minimal
|| settings->floats.video_openxr_menu_xr_pitch != defaults->floats.video_openxr_menu_xr_pitch)
config_set_float(conf, "video_openxr_menu_xr_pitch", settings->floats.video_openxr_menu_xr_pitch);
else
config_unset(conf, "video_openxr_menu_xr_pitch");
if ( !minimal
|| settings->floats.video_openxr_menu_xr_roll != defaults->floats.video_openxr_menu_xr_roll)
config_set_float(conf, "video_openxr_menu_xr_roll", settings->floats.video_openxr_menu_xr_roll);
else
config_unset(conf, "video_openxr_menu_xr_roll");
if ( !minimal
|| settings->floats.video_openxr_menu_xr_scale != defaults->floats.video_openxr_menu_xr_scale)
config_set_float(conf, "video_openxr_menu_xr_scale", settings->floats.video_openxr_menu_xr_scale);
else
config_unset(conf, "video_openxr_menu_xr_scale");
if ( !minimal
|| settings->bools.video_openxr_menu_placed != defaults->bools.video_openxr_menu_placed)
config_set_string(conf, "video_openxr_menu_placed",
settings->bools.video_openxr_menu_placed ? "true" : "false");
else
config_unset(conf, "video_openxr_menu_placed");
for (i = 0; i < MAX_USERS; i++)
{
size_t _len;
@@ -10480,6 +10768,79 @@ int8_t config_save_overrides(enum override_type type,
}
#endif
for (i = 0; i < RETRO_VIDEO_VIEWS_MAX; i++)
{
char cfg[64];
size_t len = snprintf(cfg, sizeof(cfg), "video_screen%u_xr_", i + 1);
if (settings->floats.video_screen_xr_x[i] != overrides->floats.video_screen_xr_x[i])
{
strlcpy_lit(cfg + len, "x", sizeof(cfg) - len);
config_set_float(conf, cfg, overrides->floats.video_screen_xr_x[i]);
}
if (settings->floats.video_screen_xr_y[i] != overrides->floats.video_screen_xr_y[i])
{
strlcpy_lit(cfg + len, "y", sizeof(cfg) - len);
config_set_float(conf, cfg, overrides->floats.video_screen_xr_y[i]);
}
if (settings->floats.video_screen_xr_z[i] != overrides->floats.video_screen_xr_z[i])
{
strlcpy_lit(cfg + len, "z", sizeof(cfg) - len);
config_set_float(conf, cfg, overrides->floats.video_screen_xr_z[i]);
}
if (settings->floats.video_screen_xr_yaw[i] != overrides->floats.video_screen_xr_yaw[i])
{
strlcpy_lit(cfg + len, "yaw", sizeof(cfg) - len);
config_set_float(conf, cfg, overrides->floats.video_screen_xr_yaw[i]);
}
if (settings->floats.video_screen_xr_pitch[i] != overrides->floats.video_screen_xr_pitch[i])
{
strlcpy_lit(cfg + len, "pitch", sizeof(cfg) - len);
config_set_float(conf, cfg, overrides->floats.video_screen_xr_pitch[i]);
}
if (settings->floats.video_screen_xr_roll[i] != overrides->floats.video_screen_xr_roll[i])
{
strlcpy_lit(cfg + len, "roll", sizeof(cfg) - len);
config_set_float(conf, cfg, overrides->floats.video_screen_xr_roll[i]);
}
if (settings->floats.video_screen_xr_scale[i] != overrides->floats.video_screen_xr_scale[i])
{
strlcpy_lit(cfg + len, "scale", sizeof(cfg) - len);
config_set_float(conf, cfg, overrides->floats.video_screen_xr_scale[i]);
}
if (settings->bools.video_screen_xr_visible[i] != overrides->bools.video_screen_xr_visible[i])
{
strlcpy_lit(cfg + len, "visible", sizeof(cfg) - len);
config_set_string(conf, cfg,
overrides->bools.video_screen_xr_visible[i] ? "true" : "false");
}
}
if (settings->floats.video_openxr_menu_xr_x != overrides->floats.video_openxr_menu_xr_x)
config_set_float(conf, "video_openxr_menu_xr_x", overrides->floats.video_openxr_menu_xr_x);
if (settings->floats.video_openxr_menu_xr_y != overrides->floats.video_openxr_menu_xr_y)
config_set_float(conf, "video_openxr_menu_xr_y", overrides->floats.video_openxr_menu_xr_y);
if (settings->floats.video_openxr_menu_xr_z != overrides->floats.video_openxr_menu_xr_z)
config_set_float(conf, "video_openxr_menu_xr_z", overrides->floats.video_openxr_menu_xr_z);
if (settings->floats.video_openxr_menu_xr_yaw != overrides->floats.video_openxr_menu_xr_yaw)
config_set_float(conf, "video_openxr_menu_xr_yaw", overrides->floats.video_openxr_menu_xr_yaw);
if (settings->floats.video_openxr_menu_xr_pitch != overrides->floats.video_openxr_menu_xr_pitch)
config_set_float(conf, "video_openxr_menu_xr_pitch", overrides->floats.video_openxr_menu_xr_pitch);
if (settings->floats.video_openxr_menu_xr_roll != overrides->floats.video_openxr_menu_xr_roll)
config_set_float(conf, "video_openxr_menu_xr_roll", overrides->floats.video_openxr_menu_xr_roll);
if (settings->floats.video_openxr_menu_xr_scale != overrides->floats.video_openxr_menu_xr_scale)
config_set_float(conf, "video_openxr_menu_xr_scale", overrides->floats.video_openxr_menu_xr_scale);
if (settings->bools.video_openxr_menu_placed != overrides->bools.video_openxr_menu_placed)
config_set_string(conf, "video_openxr_menu_placed",
overrides->bools.video_openxr_menu_placed ? "true" : "false");
for (i = 0; i < MAX_USERS; i++)
{
size_t _len;
+41
View File
@@ -316,6 +316,10 @@ typedef struct settings
unsigned video_openxr_refresh_rate;
unsigned video_openxr_controllers;
unsigned video_openxr_laser;
unsigned video_openxr_laser_beam;
unsigned video_openxr_edit_snap_angles;
unsigned video_openxr_edit_hinge_gap;
unsigned video_openxr_edit_smoothing;
unsigned video_openxr_placement;
unsigned video_fse_negotiation;
unsigned video_hw_decode;
@@ -582,6 +586,21 @@ typedef struct settings
struct
{
float placeholder;
float video_screen_xr_x[RETRO_VIDEO_VIEWS_MAX];
float video_screen_xr_y[RETRO_VIDEO_VIEWS_MAX];
float video_screen_xr_z[RETRO_VIDEO_VIEWS_MAX];
float video_screen_xr_yaw[RETRO_VIDEO_VIEWS_MAX];
float video_screen_xr_pitch[RETRO_VIDEO_VIEWS_MAX];
float video_screen_xr_roll[RETRO_VIDEO_VIEWS_MAX];
float video_screen_xr_scale[RETRO_VIDEO_VIEWS_MAX];
float video_openxr_menu_xr_x;
float video_openxr_menu_xr_y;
float video_openxr_menu_xr_z;
float video_openxr_menu_xr_yaw;
float video_openxr_menu_xr_pitch;
float video_openxr_menu_xr_roll;
float video_openxr_menu_xr_scale;
float video_aspect_ratio;
float video_vp_bias_x;
float video_vp_bias_y;
@@ -677,6 +696,8 @@ typedef struct settings
struct
{
bool placeholder;
bool video_screen_xr_visible[RETRO_VIDEO_VIEWS_MAX];
bool video_openxr_menu_placed;
#ifdef HAVE_VIDEO_WINDOWS
bool video_windows_enable;
bool video_screen_window_fullscreen[RETRO_VIDEO_VIEWS_MAX];
@@ -698,7 +719,10 @@ typedef struct settings
bool video_smooth;
bool video_stereo_swap_eyes;
bool video_openxr_enable;
bool video_openxr_menu_follow;
bool video_openxr_perf_boost;
bool video_openxr_edit_pause;
bool video_openxr_edit_snap;
bool video_ctx_scaling;
bool video_force_aspect;
bool video_frame_delay_auto;
@@ -1728,6 +1752,23 @@ bool config_overlay_enable_default(void);
bool config_metal_arg_buffers_default(void);
#endif
void config_reset_video_screen_xr(settings_t *settings, unsigned first, unsigned count);
/* The headset's menu back in front of screen 1. */
void config_reset_video_menu_xr(settings_t *settings);
struct video_screen_xr;
/* Edit Screens' reads and writes of a screen's place, or the menu's,
* as the Stereo 3D & Screens rows keep them. A write normalises, marks
* the settings modified and leaves Visible alone; NULL resets a screen
* as Reset Screen does, or puts the menu back in front of screen 1. The
* menu's read is false while it follows screen 1. */
void config_get_video_screen_xr(unsigned screen, struct video_screen_xr *out);
bool config_get_video_menu_xr(struct video_screen_xr *out);
void config_set_video_screen_xr(unsigned screen,
const struct video_screen_xr *values);
void config_set_video_menu_xr(const struct video_screen_xr *values);
void config_set_defaults(settings_t *target);
void config_load(void);
+277 -4
View File
@@ -19,15 +19,285 @@ drivers, and on the Meta Quest with `vulkan` and `gl`.
At run time an OpenXR runtime has to be active (SteamVR or Monado on
Linux). Then turn on **Settings > Video > Stereo 3D & Screens > Headset
Output (OpenXR)**. The other rows there are Headset Refresh Rate, Screen
Distance, Screen Width, Screen Placement (Fixed in Room, Follow Head,
Lazy Follow), Headset Controllers and Laser Pointer. The Recenter
Headset Screens and Laser Pointer (Toggle) hotkeys are unbound by
Output (OpenXR)**. Under it come Edit Screens, Snap to Screens, Snap to
Angles, Hinge Gap, Edit Smoothing and While Editing Screens, described
below, then Headset Refresh Rate, Headset Controllers, Laser Pointer,
Laser Beam, Screen Placement (Fixed in Room, Follow Head, Lazy Follow),
Screen Distance and Screen Width. The Recenter Headset Screens, Laser
Pointer (Toggle) and Edit Headset Screens hotkeys are unbound by
default.
**Laser Beam** draws a thin line from each controller to its laser's
dot, turned to face you. It shows only where the dot can: outside edit
mode, Laser Pointer Off, or its hotkey, hides both; while editing, both
hands always have a laser, and the hotkey only switches Laser Pointer
for afterwards. Edit Mode Only (the default) shows it while editing
screens, Always also for the menu and light guns, Off never. Pointing
at nothing, the line is 1.5 m long and fades out.
Without a runtime or a headset, RetroArch says so once and shows its
window as usual.
## Adjusting floating screens
In **Settings > Video > Stereo 3D & Screens**, use **Screen** to choose
which floating screen to adjust. The number is the core's logical screen
ID plus one, from 1 to 8. Only screens in the current frame's map are
listed; a tracked projection's eyes are excluded. Without a map,
including with no content loaded, Screen 1 is listed: a core without a
view map shows its whole frame as Screen 1, which these rows then move
like any other. Changing the order of views, or temporarily removing a
screen, preserves that screen's saved adjustments.
- **Horizontal Offset**, **Vertical Offset** and **Depth Offset** move
the selected screen relative to its normal placement, from -10 to +10
metres. Positive values move it right, up and towards you respectively,
in the shared placement anchor's coordinates.
- **Yaw**, **Pitch** and **Roll** rotate it from -180 to +180 degrees.
**Scale** changes its width and height together, from 0.1 to 10 times
its normal size.
- **Visible** hides or shows it. The menu remains available even when
every floating screen is hidden or moved out of sight.
- **Reset Screen** restores the selected screen's zero offsets, zero
rotations, unit scale and visibility. **Reset All Screens** does this
for all eight screen IDs, including those absent from the current map.
Reset All Screens remains available when there is no floating screen
to select.
These adjustments apply to floating screens, including the two eyes of
a stereoscopic screen together. They do not change a tracked core's eye
poses or fields of view. Changes also take effect while content is paused.
The shared Screen Distance, Screen Width, Screen Placement and layout
settings still determine the base placement. **Recenter Headset Screens**
updates the shared anchor for all screens while preserving their relative
offsets, rotations, scales and visibility; it does not reset them.
The menu opens at its own spot, straight ahead of the anchor. With
**Menu in Front of Screen 1** on, it floats 10 cm in front of Screen 1
and faces the way it does, so moving Screen 1 moves the menu with it. It
looks as large as it does at its own spot, and comes no nearer to you
than 40 cm. While Screen 1 is hidden, is a tracked core's projection or
faces away from you, the menu stays at its own spot. A menu placed in
edit mode (below) stays where it was put until it is reset there, or by
Reset All Screens.
Screen adjustments are configuration settings. Save the configuration to
keep them as defaults, or use **Quick Menu > Overrides** to save core,
content-directory or game overrides. Loading an override applies its
screen values; unloading it restores the inherited values. The selector
itself is only a menu choice and is not saved. Configuration keys use
`video_screenN_xr_x`, `_xr_y`, `_xr_z`, `_xr_yaw`, `_xr_pitch`, `_xr_roll`,
`_xr_scale` and `_xr_visible`, with `N` from 1 to 8. Finite values outside
the supported ranges are clamped; non-finite values use the defaults.
With the legacy **gl** driver on desktop or Quest, opening the menu shows
the whole window on an unadjusted floating screen. Per-screen adjustments
resume when the menu closes. This keeps the menu and resets reachable
without applying the primary content screen's hidden state or transform
to the menu. The desktop **glcore** and **vulkan** paths use a separate
menu screen.
## Editing screens in the headset
**Edit Screens** moves the floating screens and the menu with the
controllers instead of the menu's rows. Start it from **Settings > Video >
Stereo 3D & Screens > Edit Screens** (shown with Headset Output on; the
menu closes into it), the **Edit Headset Screens** hotkey, the headset's
own **Edit Screens** action, or the `HEADSET_EDIT_SCREENS` network
command. It needs content running. The same hotkey, action or command
stops it, as do opening the menu, loading or closing content, and the
end of the headset session. The hotkey and the action are unbound by
default; bind the action in the runtime's controller settings, in both
RetroArch's game sets and its Edit Screens set. If the runtime refuses
the Edit Screens set's bindings for a hand's controller, Edit Screens
says it is unavailable. It also stops and says so when the runtime
binds neither that set's Grab nor its RetroArch Menu, as a controller
binding saved in the runtime before RetroArch had them can.
While editing, every item has a faint outline just outside it, a light
line between two dark ones so it shows against any border. It is bright
on what a hand holds, and on what a laser points at unless an item is
chosen (below). Both hands have a laser whatever **Laser Pointer** is
set to, with a beam unless **Laser Beam** is Off. The menu also shows a
handle bar under it in the same light and dark, faint, and bright when
the menu's outline is. While a held screen is hinged (see **Snap to
Screens**), its edge and the edge it hinges on lose their outline, and a
6 mm green line runs along the hinge where the two overlap, midway
between the edges and drawn a centimetre nearer the eye so it shows
over both screens, while a let-go would settle the screen into the
hinge and for 0.15 s after.
Two screens no hand holds that rest exactly where a hinge holds one of
them, so that taking it up would leave it there, show the same: any
screen let go where a hinge holds it, or set out so. Screens only near
a hinge show nothing until hinged once. A screen hinged at rest on two
edges at a corner keeps one of those two rims.
The headset's controllers only edit;
keyboards and other gamepads still reach the content. The desktop
window shows a notice when editing starts, with the controls, for
about 8 seconds, and a short one when it stops. **Select
Next** and **Select Previous** choose an item, below.
| Controller | Grab | Tilt | Distance and size | Turn and tilt (other hand) | Reset | Menu | Select Next / Previous |
| --- | --- | --- | --- | --- | --- | --- | --- |
| Steam Frame | grip | trigger | stick | stick | A; left hand: D-pad down | right stick click | left D-pad right / left |
| Valve Index | grip | trigger | stick | stick | A | left trackpad press | right B / left B |
| Oculus Touch | grip | trigger | stick | stick | X / A | menu button | B / Y |
| HTC Vive | grip | trigger | trackpad | trackpad | trackpad click | left menu button | right menu button / - |
| Simple controller | select | - | - | - | - | left menu button | - |
- A grip on an item grabs it: it keeps its place relative to the hand
and follows it. Each hand holds one item; the other hand on the same
item does nothing.
- Items keep the pitch and roll they had when grabbed, and their angle
to the way the hand points across the floor: they move with the hand
and turn left and right as it does, but raising, lowering or
twisting the hand does not tip, roll or swivel them. Pointing nearly
straight up or down, a twist of the hand turns them instead, and
tipping it 5 degrees past straight up or down turns them round to
face the other way. Hold the trigger while grabbing to let them ride
the hand, pitch and roll too; let go of the trigger to keep them.
- The other hand's stick, while that hand holds nothing, turns and
tilts the held item about its middle, whether or not the trigger is
held, faster the further the stick is pushed, with no stop. Right
turns it about the vertical, keeping its pitch and roll,
anticlockwise as seen from above, so a screen facing you turns
towards your right; left the other way. Up tips its top back about
its own left-right line, as when pushing a monitor's top back; down
the other way. Within about 25 degrees of left or right the stick
only turns, and within about 25 degrees of up or down it only tilts;
pushed clearly diagonally, it does both at once. As the stick turns
and tilts it, the item clicks into steps of Snap to Angles, counted
from facing you and from upright: it holds within about 2 degrees of
each and the holding hand buzzes once; pushing on leaves the step.
Carrying it or tilting it with the trigger never clicks. Snapping
still catches it parallel or square, and wins over a step.
- On the holding hand's stick, up pushes the item away along the laser
and down pulls it closer; right makes it larger and left smaller,
faster the further the stick is pushed.
- Offsets stop at +-10 m and scale at 0.1 to 10, as in the menu: the
item waits at the limit and moves on from there once the hand comes
back.
- Reset on a screen does what Reset Screen does; on the menu it makes
the menu follow Screen 1 again.
- The menu is grabbed by its handle bar, even where a screen is nearer,
and within about 3 cm of the bar where the laser meets no other item;
elsewhere on the menu the grip takes the screen behind. Once a laser
is on the bar, the bar stays its target while the laser stays within
those 3 cm, even over a screen; a laser that comes into the 3 cm from
a screen keeps the screen. Grabbing the menu places it where it
shows, even without moving it, so it no longer follows Screen 1. The
legacy **gl** driver draws the menu in its window, so only screens
can be grabbed there.
- Select Next and Select Previous choose an item: they step from
Laser through each screen that can be moved, in number order (a 3D
screen is one), then the menu while it is shown, and back to Laser.
Laser is the usual way: grip and Reset act on what a laser points at.
With an item chosen, its outline is bright and pointing lights
nothing else; either hand's grip takes it from wherever the
hand is, as if on a stick, and Reset resets it. While one hand holds
it, the other points, grabs and resets as usual. The choice stays
after letting go, until stepped back to Laser or edit mode ends; a
chosen screen that is hidden or goes away goes back to Laser. This
takes a screen hidden behind another.
- A faint tag just above the hand that last pressed Select Next or
Select Previous (the left hand at first) names the chosen item:
Laser, Screen 1, Screen 2 and so on, or Menu.
- Seen from behind, a screen or the menu shows a plain dark back.
Edit mode grabs and outlines it from there as from the front.
- A head-tracked core's projection cannot be grabbed, nor can the whole
frame a views core falls back to when its screens cannot be placed. In
Follow Head a grabbed screen keeps its place in view; Lazy Follow does
not glide while anything is held. Recenter lets go of what is held
first. A hand that loses tracking leaves its item still.
**While Editing Screens** chooses whether the content keeps running (the
default) or pauses; leaving edit mode resumes only content it paused.
**Snap to Screens** (on by default) lines a held screen up with the
other screens within about 30 cm of it that no hand holds. Within 5
degrees of facing the same way as one, or of a quarter turn from it
about its vertical, the held screen turns exactly so; of two, it turns
to the one whose edge is nearer. Then, within 3 cm, it moves into the
other's plane, and its left, right, top, bottom or centre lines up with
the other's, level; edge to edge, or at a corner for a screen at a
quarter turn, it keeps the gap described under Hinge Gap. Or, at any
angle between them, an edge of the held screen hinges on an edge of the
other where the two overlap along it, top, bottom or side, within 5
degrees of parallel and 3 cm of its plane, up to 10 cm out from the
edge: it turns exactly parallel to that edge and moves into the other's
plane, keeping its gap from that edge, centres or ends level along it,
keeping its angle, like the two halves of a DS. The green line
shows wherever a let-go would settle the screen into that hinge, and
0.15 s longer once it leaves, so a shaking hand never flickers it; it
stays when the screen is let go there. A screen taken up where it rests
hinged starts hinged, with no buzz, until it is carried out of the
hinge it rests in, another hinge takes hold or the other hand takes
the screen it hinges on. At a fold or a square corner with a gap
only the screen carried in does: the other one's edge is off its
plane, so it is taken up free. Screens that would overlap do not
hinge. While a screen is hinged, the other hand's stick swings it
about the shared edge, the tilt for a top or bottom edge and the
turn for a side one, and it stays where the swing leaves it. The
swing lets go just past square, but that axis keeps pivoting it on the
same edge until the edge is carried out of the hinge's reach or the
other hand takes the screen it hinges on; the stick's other axis turns
or tilts it as before and can pull it off. Past
each 3 cm range and the 1 cm click the pull fades out over as far again,
a hinge fades out from 10 to 13 cm, and from 30 to 60 cm away every pull
fades, so the screen catches up with the hand instead of jumping. A
screen just taken does not move: snapping fades in as it is carried,
whole once it is 5 cm or 10 degrees from where it was taken, but a
hinge it rests in, where holding it moves nothing, holds at once. The
holding hand gets a short buzz as a snap takes hold, and none again from
that snap until the screen has left its pull. A hinge taking hold gets a
longer, stronger buzz instead, and short buzzes due while it plays are
skipped; it comes again only once the hinge has let go, 0.15 s after
the screen leaves where it would settle, or for another edge, so a
swing past square and back, or a wobble where the hinge takes hold,
does not repeat it. The menu neither snaps nor is
snapped to. Let go, it stays where, turned and as large as it was a
moment (0.08 s) before the grip opened, so the hand's twitch as it
opens does not move it. With Edit Smoothing on, which damps that
twitch, it stays where, turned and as large as it shows, unless it
settles into a hinge. That is where it is saved.
**Snap to Angles** (15 degrees by default) is the step the other hand's
stick turns and tilts a held item in: 15, 30, 45 or 90 degrees. A
hinged screen's steps count from flat with the other screen, both ways.
Off turns and tilts it smoothly, with no steps and no buzz.
**Hinge Gap** (2 cm by default) is the gap Snap to Screens leaves
between screens that meet edge to edge, side by side, at a corner or
hinged at an angle: 0, 1, 2, 3, 5 or 10 cm. A screen within 1 cm of it
clicks to it exactly, with a short buzz; let go within 3 cm of it, or
within 6 cm of level along the edge, a hinged screen settles there
exactly; anywhere else from touching to 10 cm the gap stays where the
hand puts it, still hinged, and from 10 to 13 cm the hinge lets go. At
0 the edges touch. It does nothing while Snap to Screens is off;
screens already placed do not move. Pushed past touching, a hinged
screen stops there, so two screens can be pushed together with no gap
and let go touching, with one short buzz per push (below a 3 cm gap,
the click's). From 3 cm past (3.5 cm at a 1 cm gap) the hinge lets go
and the screen slides on into the other, free from 5.5 cm (6 cm);
pulled back, it hinges again 5 mm inside that 3 cm. With Snap to
Screens off nothing stops them, and screens overlap freely.
**Edit Smoothing** (Medium by default) steadies the controllers' aim
while editing, so a screen held far out on the laser does not shake
with the hand: a slow or still hand is smoothed a lot and a fast one
little, so quick moves keep up. Low, Medium and High smooth more in
turn; Off uses the aim as tracked. Everything edit mode does with the
lasers follows the smoothed aim: the dots and beams, what lights, what
a grip takes, the stick and what is held; a screen is saved where it
was shown. The lasers are never smoothed outside edit mode.
Edits change the same settings as the menu's rows and are saved the
same way: on quit while **Save Configuration on Quit** is on (the
default), or by saving the configuration or an override.
The menu's own place is saved as `video_openxr_menu_xr_x`, `_xr_y`,
`_xr_z`, `_xr_yaw`, `_xr_pitch`, `_xr_roll`, `_xr_scale` and
`video_openxr_menu_placed`; while that is false the menu follows Screen 1.
## What a core sees
A core describes its frame with `RETRO_ENVIRONMENT_SET_VIDEO_VIEWS`: a
@@ -123,3 +393,6 @@ host. Model it on the three above:
headset in headless gamescope, through a test API layer
(`samples/openxr/test_layer`) that records every frame's layers and
images. It needs a GPU, so it is not run in CI.
- The `edit-*` cases drive edit mode through the test layer's scripted
aims (`aim ... [<roll>]`) and `edit/*` actions on Vulkan, glcore and
gl.
+25 -7
View File
@@ -402,17 +402,26 @@ struct gl_openxr_dot
static bool gl_openxr_cursor_fill(void *user, uint32_t index,
const char **step, int *code)
{
uint8_t px[VIDEO_XR_CURSOR_DIM * VIDEO_XR_CURSOR_DIM * 4];
GLenum err;
struct gl_openxr_dot *dot = (struct gl_openxr_dot*)user;
gl_openxr_t *xr = dot->xr;
video_xr_cursor_pixels(px);
unsigned w = openxr_frame_cursor_w(&xr->frame);
uint8_t *px = (uint8_t*)malloc(
(size_t)VIDEO_XR_CURSOR_W * VIDEO_XR_CURSOR_H * 4);
if (!px)
{
*step = "malloc";
*code = 0;
return false;
}
openxr_frame_cursor_pixels(px, w, true);
while (xr->gl.GetError() != GL_NO_ERROR) { }
xr->gl.BindTexture(GL_TEXTURE_2D, dot->images[index][0]);
xr->gl.PixelStorei(GL_UNPACK_ALIGNMENT, 4);
xr->gl.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_XR_CURSOR_DIM,
VIDEO_XR_CURSOR_DIM, GL_RGBA, GL_UNSIGNED_BYTE, px);
xr->gl.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, VIDEO_XR_CURSOR_H,
GL_RGBA, GL_UNSIGNED_BYTE, px);
xr->gl.BindTexture(GL_TEXTURE_2D, 0);
free(px);
if ((err = xr->gl.GetError()) == GL_NO_ERROR)
return true;
*step = "glTexSubImage2D";
@@ -432,11 +441,20 @@ static void gl_openxr_cursor_create(gl_openxr_t *xr)
li.sc = &xr->sc;
li.tex = dot.images;
xr->frame.cursor = openxr_swapchains_create_filled(&xr->sc, formats, 2,
VIDEO_SCALE_PACK(VIDEO_XR_CURSOR_DIM, VIDEO_XR_CURSOR_DIM),
VIDEO_SCALE_PACK(openxr_frame_cursor_w(&xr->frame),
VIDEO_XR_CURSOR_H),
openxr_binding_gl_images, &li, gl_openxr_cursor_fill, &dot,
&step, &code);
if (xr->frame.cursor)
RARCH_LOG("[OpenXR] Laser cursor ready.\n");
if (xr->frame.cursor && xr->frame.rt->max_width)
RARCH_LOG("[OpenXR] Laser cursor ready: %u wide, with %s (the "
"headset's widest %u).\n", openxr_frame_cursor_w(&xr->frame),
openxr_frame_cursor_parts(&xr->frame),
xr->frame.rt->max_width);
else if (xr->frame.cursor)
RARCH_LOG("[OpenXR] Laser cursor ready: %u wide, with %s (the "
"headset does not say its widest).\n",
openxr_frame_cursor_w(&xr->frame),
openxr_frame_cursor_parts(&xr->frame));
else
RARCH_WARN("[OpenXR] No laser cursor (%s, %d).\n", step, code);
}
+397 -35
View File
@@ -13,6 +13,7 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -21,19 +22,27 @@
#include <retro_atomic.h>
#include <retro_miscellaneous.h>
#include <retro_timers.h>
#include <compat/strl.h>
#include <encodings/utf.h>
#include "openxr_frame.h"
#include "../video_defines.h"
#include "../font_driver.h"
#include "../../msg_hash.h"
#include "../../runloop.h"
#include "../../verbosity.h"
/* The seqlock's copy holds the largest thing it publishes. */
#define OPENXR_SEQ_WORDS \
((OPENXR_WORDS(openxr_grabs_t) > OPENXR_WORDS(video_xr_quad_set_t)) \
? OPENXR_WORDS(openxr_grabs_t) : OPENXR_WORDS(video_xr_quad_set_t))
typedef char openxr_seq_fits[
( OPENXR_WORDS(openxr_tracked_t) <= OPENXR_WORDS(video_xr_quad_set_t)
&& OPENXR_WORDS(openxr_screens_t) <= OPENXR_WORDS(video_xr_quad_set_t)
&& OPENXR_WORDS(openxr_projection_t) <= OPENXR_WORDS(video_xr_quad_set_t))
( OPENXR_WORDS(openxr_tracked_t) <= OPENXR_SEQ_WORDS
&& OPENXR_WORDS(openxr_screens_t) <= OPENXR_SEQ_WORDS
&& OPENXR_WORDS(openxr_projection_t) <= OPENXR_SEQ_WORDS
&& OPENXR_WORDS(openxr_aims_t) <= OPENXR_SEQ_WORDS)
? 1 : -1];
/* A latched seqlock: two copies of the words, one writer, any number
@@ -54,7 +63,7 @@ static void openxr_seq_store(retro_atomic_int_t *words,
static void openxr_seq_publish(retro_atomic_int_t *seq,
retro_atomic_int_t *words, const void *src, size_t len)
{
int tmp[OPENXR_WORDS(video_xr_quad_set_t)];
int tmp[OPENXR_SEQ_WORDS];
size_t n = (len + sizeof(int) - 1) / sizeof(int);
int s = retro_atomic_load_relaxed_int(seq);
@@ -71,7 +80,7 @@ static void openxr_seq_publish(retro_atomic_int_t *seq,
static void openxr_seq_read(retro_atomic_int_t *seq,
retro_atomic_int_t *words, void *dst, size_t len)
{
int tmp[OPENXR_WORDS(video_xr_quad_set_t)];
int tmp[OPENXR_SEQ_WORDS];
size_t n = (len + sizeof(int) - 1) / sizeof(int);
for (;;)
{
@@ -169,37 +178,150 @@ static const XrSwapchain *openxr_frame_chains(openxr_slot_t *slot,
return (*layers && slot->old[0]) ? slot->old : NULL;
}
/* The published quads whose slot shows an image. No slot changes
* under it: slots change under the graphics lock, or on this thread
* where the host has none. */
static unsigned openxr_frame_layers(openxr_frame_t *f,
/* Every quad of slot's item at pose and g's size, both eyes of a stereo
* screen; a fixed slot is no item. */
static void openxr_frame_hold_slot(video_xr_quad_set_t *set, unsigned slot,
const video_xr_pose_t *pose, const video_xr_grab_t *g)
{
unsigned i;
if (set->slots[slot].fixed)
return;
for (i = 0; i < set->num_quads; i++)
if (set->quads[i].slot == slot)
{
set->quads[i].pose = *pose;
set->quads[i].width = g->width;
set->quads[i].height = g->height;
}
}
/* Edit mode, in the screens' frame: a held item goes where the hand's
* aim at time puts it, kept while untracked, snapped and hinged as the
* main thread does; a let-go one stays until quads planned at it show
* it. */
static void openxr_frame_hold(openxr_frame_t *f, XrTime time,
video_xr_quad_set_t *set)
{
unsigned h, s;
video_xr_pose_t inv;
unsigned holding = 0;
XrSpace space = f->screens.view
? f->session->view_space : f->session->local_space;
for (s = 0; s < VIDEO_XR_MAX_SLOTS; s++)
{
const video_xr_grab_t *g = &f->grabs.let_go[s];
if ( g->state == VIDEO_XR_GRAB_RELEASED
&& (int)(set->slots[s].edit_seq - g->release_seq) < 0)
openxr_frame_hold_slot(set, s, &g->last, g);
}
for (h = 0; h < 2; h++)
if ( f->grabs.hand[h].state == VIDEO_XR_GRAB_HELD
&& f->grabs.hand[h].slot < VIDEO_XR_MAX_SLOTS)
holding |= 1u << f->grabs.hand[h].slot;
video_xr_pose_invert(&f->screens.pose, &inv);
f->hinged = 0;
for (h = 0; h < 2; h++)
{
video_xr_pose_t aim;
const video_xr_grab_t *g = &f->grabs.hand[h];
openxr_held_t *held = &f->held[h];
if (held->state != g->state || held->slot != g->slot)
{
held->pose = g->last;
held->slot = g->slot;
held->state = g->state;
held->hinged = false;
}
if (g->state != VIDEO_XR_GRAB_HELD || g->slot >= VIDEO_XR_MAX_SLOTS)
continue;
if (f->aim && time && f->aim(f->extra_user, h, space, time, &aim))
{
const video_xr_quad_set_t *targets = f->grabs.snap ? set : NULL;
video_xr_compose(&inv, &aim, &aim);
video_xr_grab_hinge(g, &aim, targets, holding,
&f->grabs.start[h], &held->pose, NULL);
}
openxr_frame_hold_slot(set, g->slot, &held->pose, g);
/* The hinge the main thread marks, nothing of the frame's own:
* a wobble between its polls never flips the line. */
held->hinged = video_xr_grab_marked(g, &held->pose, set, holding,
&held->hinge);
if (held->hinged)
{
video_xr_pose_t at;
video_xr_vec3_t dir, flat;
video_xr_hinge_t *k = &f->hinges[h];
*k = held->hinge;
video_xr_pose_identity(&at);
at.position = k->point;
video_xr_compose(&f->screens.pose, &at, &at);
k->point = at.position;
dir = k->dir;
flat = k->flat;
video_xr_rotate(&f->screens.pose.orientation, &dir, &k->dir);
video_xr_rotate(&f->screens.pose.orientation, &flat, &k->flat);
f->hinged |= 1u << h;
}
}
}
static bool openxr_frame_head(openxr_frame_t *f, XrTime time,
video_xr_pose_t *head);
/* This frame's quads, holds applied, where the screens are, into
* f->shown; layers of those whose slot shows an image. Runtimes draw
* only a quad's front, so with the head behind one its back, the laser
* image's grey square, takes its layer, turned to the head. No slot
* changes under it: slots change under the graphics lock, or on this
* thread where the host has none. */
static unsigned openxr_frame_layers(openxr_frame_t *f, XrTime time,
XrCompositionLayerQuad *layers,
const XrCompositionLayerBaseHeader **ptrs)
{
unsigned i;
unsigned n = 0;
video_xr_quad_set_t quads;
video_xr_pose_t head;
unsigned n = 0;
bool seen = false;
video_xr_quad_set_t *quads = &f->shown;
openxr_seq_read(&f->quads_seq, f->quads_words,
&quads, sizeof(quads));
for (i = 0; i < quads.num_quads; i++)
quads, sizeof(*quads));
openxr_frame_hold(f, time, quads);
f->backs = 0;
f->menu_layer = -1;
/* Follow Head's space is the head's own. Backs are for edit mode,
* where screens are turned around; outside it nothing changes. */
if (f->cursor && f->grabs.editing)
{
if (f->screens.view)
{
video_xr_pose_identity(&head);
seen = true;
}
else
seen = openxr_frame_head(f, time, &head);
}
for (i = 0; i < quads->num_quads; i++)
{
video_xr_pose_t pose;
unsigned dims, count;
const XrSwapchain *chains;
const video_xr_quad_t *q = &quads.quads[i];
openxr_slot_t *slot = &f->sc->slots[q->slot];
XrCompositionLayerQuad *l = &layers[n];
video_xr_pose_t pose;
bool back;
video_xr_quad_t *q = &quads->quads[i];
openxr_slot_t *slot = &f->sc->slots[q->slot];
XrCompositionLayerQuad *l = &layers[n];
video_xr_compose(&f->screens.pose, &q->pose, &q->pose);
if ( !(chains = openxr_frame_chains(slot, &dims, &count))
|| q->layer >= count)
continue;
video_xr_compose(&f->screens.pose, &q->pose, &pose);
back = seen && video_xr_quad_behind(q, &head.position);
pose = q->pose;
l->type = XR_TYPE_COMPOSITION_LAYER_QUAD;
l->next = NULL;
l->subImage.imageRect.offset.x = 0;
l->subImage.imageRect.offset.y = 0;
l->subImage.imageArrayIndex = 0;
/* The UI is drawn over transparent black: premultiplied. */
l->layerFlags = (q->kind == VIDEO_XR_QUAD_MENU)
l->layerFlags = (q->kind == VIDEO_XR_QUAD_MENU && !back)
? XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT : 0;
l->space = f->screens.view
? f->session->view_space : f->session->local_space;
@@ -207,9 +329,24 @@ static unsigned openxr_frame_layers(openxr_frame_t *f,
? XR_EYE_VISIBILITY_LEFT
: ((q->eye == VIDEO_XR_EYE_RIGHT)
? XR_EYE_VISIBILITY_RIGHT : XR_EYE_VISIBILITY_BOTH);
l->subImage.swapchain = chains[q->layer];
l->subImage.imageRect.extent.width = (int32_t)VIDEO_SCALE_W(dims);
l->subImage.imageRect.extent.height = (int32_t)VIDEO_SCALE_H(dims);
if (back)
{
video_xr_turn_back(&pose);
f->backs |= 1u << i;
l->subImage.swapchain = f->cursor;
l->subImage.imageRect.offset.x =
VIDEO_XR_BACK_X + VIDEO_XR_BACK_MARGIN;
l->subImage.imageRect.offset.y =
VIDEO_XR_BACK_Y + VIDEO_XR_BACK_MARGIN;
l->subImage.imageRect.extent.width = VIDEO_XR_BACK_DIM;
l->subImage.imageRect.extent.height = VIDEO_XR_BACK_DIM;
}
else
{
l->subImage.swapchain = chains[q->layer];
l->subImage.imageRect.extent.width = (int32_t)VIDEO_SCALE_W(dims);
l->subImage.imageRect.extent.height = (int32_t)VIDEO_SCALE_H(dims);
}
l->pose.orientation.x = pose.orientation.x;
l->pose.orientation.y = pose.orientation.y;
l->pose.orientation.z = pose.orientation.z;
@@ -219,6 +356,8 @@ static unsigned openxr_frame_layers(openxr_frame_t *f,
l->pose.position.z = pose.position.z;
l->size.width = q->width;
l->size.height = q->height;
if (q->kind == VIDEO_XR_QUAD_MENU)
f->menu_layer = (int)n;
ptrs[n++] = (const XrCompositionLayerBaseHeader*)l;
}
return n;
@@ -353,8 +492,19 @@ static void openxr_frame_screens_anchor(openxr_frame_t *f,
static void openxr_frame_place_screens(openxr_frame_t *f, XrTime time)
{
video_xr_pose_t at;
bool held;
int recenters = retro_atomic_load_acquire_int(&f->recenters);
int placement = retro_atomic_load_acquire_int(&f->placement);
int grabs = retro_atomic_load_acquire_int(&f->grabs_seq);
/* Outside edit mode nothing is published: no copy. */
if (grabs != f->grabs_read)
{
openxr_seq_read(&f->grabs_seq, f->grabs_words, &f->grabs,
sizeof(f->grabs));
f->grabs_read = grabs;
}
held = f->grabs.hand[0].state == VIDEO_XR_GRAB_HELD
|| f->grabs.hand[1].state == VIDEO_XR_GRAB_HELD;
openxr_frame_screens_anchor(f, &at);
f->screens.view = (placement == VIDEO_OPENXR_PLACEMENT_FOLLOW);
if (placement != VIDEO_OPENXR_PLACEMENT_LAZY)
@@ -386,7 +536,9 @@ static void openxr_frame_place_screens(openxr_frame_t *f, XrTime time)
f->lazy_recenters = recenters;
}
}
else if ( openxr_frame_head(f, time, &head)
/* Edit mode holds the glide while anything is held. */
else if ( !held
&& openxr_frame_head(f, time, &head)
&& video_xr_anchor_from_head(&head, &target))
f->lazy_gliding = video_xr_lazy_follow(&f->lazy, &target,
(f->lazy_time > 0 && time > f->lazy_time)
@@ -582,19 +734,15 @@ static bool openxr_frame_wait_begin(openxr_frame_t *f,
/* At most the runtime's layer count: the last screens go first, so the
* menu and the host's layers over it stay. The quads are ptrs[first]
* up to ptrs[end]; returns the count kept. */
* up to ptrs[end], as openxr_frame_layers made them; returns the count
* kept. */
static uint32_t openxr_frame_fit(const openxr_frame_t *f,
const XrCompositionLayerBaseHeader **ptrs, uint32_t count,
uint32_t first, uint32_t end)
{
XrSwapchain menu = f->sc->slots[VIDEO_XR_MENU_SLOT].swapchains[0];
XrSwapchain menu_old = f->sc->slots[VIDEO_XR_MENU_SLOT].old[0];
while (count > f->rt->max_layers && end > first)
{
const XrCompositionLayerQuad *q =
(const XrCompositionLayerQuad*)ptrs[--end];
if ( q->subImage.swapchain == menu
|| q->subImage.swapchain == menu_old)
if ((int)(--end - first) == f->menu_layer)
continue;
memmove(&ptrs[end], &ptrs[end + 1],
(count - end - 1) * sizeof(*ptrs));
@@ -630,13 +778,33 @@ static void openxr_frame_submit(openxr_frame_t *f,
ptrs[end_info.layerCount++] =
(const XrCompositionLayerBaseHeader*)&proj;
first = end_info.layerCount;
end = first + openxr_frame_layers(f, layers,
ptrs + first);
end = first + openxr_frame_layers(f, b->display_time,
layers, ptrs + first);
end_info.layerCount = end;
if (f->extra_layers)
end_info.layerCount += f->extra_layers(f->extra_user,
b->display_time, ptrs + end_info.layerCount,
OPENXR_MAX_EXTRA_LAYERS);
{
unsigned i, n;
unsigned under = 0;
unsigned cap = (f->rt->max_layers > end)
? (unsigned)(f->rt->max_layers - end) : 0;
if (cap < OPENXR_KEPT_EXTRA_LAYERS)
cap = OPENXR_KEPT_EXTRA_LAYERS;
if (cap > OPENXR_MAX_EXTRA_LAYERS)
cap = OPENXR_MAX_EXTRA_LAYERS;
n = f->extra_layers(f->extra_user, b->display_time, &f->shown,
f->screens.view
? f->session->view_space : f->session->local_space,
ptrs + end, cap, &under);
end_info.layerCount += n;
/* The first under go before the quads. */
for (i = 0; i < under && i < n; i++, first++, end++)
{
const XrCompositionLayerBaseHeader *l = ptrs[end];
memmove(&ptrs[first + 1], &ptrs[first],
(end - first) * sizeof(*ptrs));
ptrs[first] = l;
}
}
end_info.layerCount = openxr_frame_fit(f, ptrs,
end_info.layerCount, first, end);
}
@@ -839,6 +1007,12 @@ void openxr_frame_deinit(openxr_frame_t *f)
f->idle_ready = false;
}
/* A new session's frame loop holds nothing, whatever the input does:
* its own copy is cleared and what is published now is passed over,
* the main thread staying the one writer of the grabs. */
static const openxr_grabs_t openxr_frame_no_grabs;
static const openxr_aims_t openxr_frame_no_aims;
void openxr_frame_session_created(openxr_frame_t *f)
{
video_xr_pose_identity(&f->tracked.anchor);
@@ -849,6 +1023,10 @@ void openxr_frame_session_created(openxr_frame_t *f)
openxr_frame_publish_tracked(f);
openxr_seq_publish(&f->screens_seq, f->screens_words,
&f->screens, sizeof(f->screens));
f->grabs = openxr_frame_no_grabs;
f->grabs_read = retro_atomic_load_acquire_int(&f->grabs_seq);
openxr_seq_publish(&f->aims_seq, f->aims_words,
&openxr_frame_no_aims, sizeof(openxr_frame_no_aims));
retro_atomic_store_release_int(&f->recenter, 0);
openxr_frame_list_rates(f);
}
@@ -881,6 +1059,7 @@ void openxr_frame_input_attach(openxr_frame_t *f)
h.hand_data_source = f->rt->hand_data_source;
f->extra_layers = in->frame_layers;
f->locate_hands = in->locate_hands;
f->aim = in->aim;
f->extra_user = in->user;
f->input_attached = true;
in->session_created(in->user, &h);
@@ -894,6 +1073,7 @@ void openxr_frame_input_detach(openxr_frame_t *f)
f->input_attached = false;
f->extra_layers = NULL;
f->locate_hands = NULL;
f->aim = NULL;
f->extra_user = NULL;
if (in->session_destroying)
in->session_destroying(in->user);
@@ -915,6 +1095,24 @@ XrSwapchain openxr_frame_cursor(const openxr_frame_t *f)
return f->cursor;
}
unsigned openxr_frame_cursor_w(const openxr_frame_t *f)
{
/* Not said: 4096, as openxr_runtime's max_dim. */
unsigned max = f->rt->max_width ? f->rt->max_width : 4096;
if (max >= VIDEO_XR_CURSOR_W)
return VIDEO_XR_CURSOR_W;
return (max >= VIDEO_XR_CURSOR_TAGGED_W)
? VIDEO_XR_CURSOR_TAGGED_W : VIDEO_XR_CURSOR_UNTAGGED_W;
}
const char *openxr_frame_cursor_parts(const openxr_frame_t *f)
{
unsigned w = openxr_frame_cursor_w(f);
if (w == VIDEO_XR_CURSOR_W)
return "cut outlines and tags";
return (w == VIDEO_XR_CURSOR_TAGGED_W) ? "tags" : "no tags";
}
bool openxr_frame_start(openxr_frame_t *f)
{
if (f->inline_host || f->thread)
@@ -1195,6 +1393,16 @@ void openxr_frame_publish(openxr_frame_t *f, const video_xr_quad_set_t *set)
openxr_seq_publish(&f->quads_seq, f->quads_words, set, sizeof(*set));
}
void openxr_frame_publish_grabs(openxr_frame_t *f, const openxr_grabs_t *g)
{
openxr_seq_publish(&f->grabs_seq, f->grabs_words, g, sizeof(*g));
}
void openxr_frame_publish_aims(openxr_frame_t *f, const openxr_aims_t *a)
{
openxr_seq_publish(&f->aims_seq, f->aims_words, a, sizeof(*a));
}
void openxr_frame_publish_projection(openxr_frame_t *f,
const openxr_projection_t *p)
{
@@ -1238,6 +1446,20 @@ void openxr_frame_get_shown_quads(openxr_frame_t *f,
*space = at.view ? f->session->view_space : f->session->local_space;
}
void openxr_frame_get_screens(openxr_frame_t *f, video_xr_pose_t *pose,
XrSpace *space)
{
openxr_screens_t at;
openxr_seq_read(&f->screens_seq, f->screens_words, &at, sizeof(at));
*pose = at.pose;
*space = at.view ? f->session->view_space : f->session->local_space;
}
void openxr_frame_get_aims(openxr_frame_t *f, openxr_aims_t *out)
{
openxr_seq_read(&f->aims_seq, f->aims_words, out, sizeof(*out));
}
void openxr_frame_request_recenter(openxr_frame_t *f)
{
retro_atomic_store_release_int(&f->recenter, 1);
@@ -1252,3 +1474,143 @@ XrDuration openxr_frame_period(openxr_frame_t *f)
{
return (XrDuration)retro_atomic_load_acquire_int(&f->period_ns);
}
/* A glyph's coverage from the font's atlas into a tag cell's cov at x,
* y, kept VIDEO_XR_TAG_INSET inside the cell. */
static void openxr_frame_tag_glyph(const struct font_atlas *atlas,
const struct font_glyph *g, int x, int y, uint8_t *cov)
{
unsigned gx, gy;
unsigned w = VIDEO_SCALE_W(g->dims);
unsigned h = VIDEO_SCALE_H(g->dims);
unsigned ax = VIDEO_SCALE_W(g->atlas_pos);
unsigned ay = VIDEO_SCALE_H(g->atlas_pos);
if (!atlas || !atlas->buffer || atlas->format != FONT_ATLAS_FORMAT_A8)
return;
for (gy = 0; gy < h && ay + gy < atlas->height; gy++)
for (gx = 0; gx < w && ax + gx < atlas->width; gx++)
{
uint8_t a;
int cx = x + (int)gx;
int cy = y + (int)gy;
if ( cx < VIDEO_XR_TAG_INSET || cy < VIDEO_XR_TAG_INSET
|| cx >= VIDEO_XR_TAG_COLS - VIDEO_XR_TAG_INSET
|| cy >= VIDEO_XR_TAG_ROWS - VIDEO_XR_TAG_INSET)
continue;
a = atlas->buffer[(ay + gy) * atlas->width + ax + gx];
if (a > cov[cy * VIDEO_XR_TAG_COLS + cx])
cov[cy * VIDEO_XR_TAG_COLS + cx] = a;
}
}
/* A line of font's, size high, in whole rows above and below its
* baseline: the font's own, or, where it gives none, a 1.25 em line
* as a common font's. */
static void openxr_frame_tag_metrics(const font_renderer_driver_t *drv,
void *font, unsigned size, int *asc, int *desc)
{
struct font_line_metrics *m = NULL;
*asc = (int)size;
*desc = (int)size / 4;
if (drv->get_line_metrics)
drv->get_line_metrics(font, &m);
if (m)
{
*asc = (int)ceil(m->ascender);
*desc = (int)ceil(m->descender);
}
}
/* text in one line, centred in a tag cell's coverage cov, the line
* (asc rows over its baseline, desc under) in the cell's text rows; a
* line too tall loses the top of its ascender, never a descender. */
static void openxr_frame_tag_line(const font_renderer_driver_t *drv,
void *font, int asc, int desc, const char *text, uint8_t *cov)
{
const char *msg = text;
size_t msg_len = strlen(text);
void *font_data = font;
const struct font_glyph *(*get_glyph)(void*, uint32_t) =
drv->get_glyph;
const struct font_glyph *glyph_q = drv->get_glyph(font, '?');
int line_x = 0;
int spare = VIDEO_XR_TAG_TEXT_ROWS - asc - desc;
int base = VIDEO_XR_TAG_ROWS
- VIDEO_XR_TAG_INSET - desc - (spare > 0 ? spare / 2 : 0);
#define FONT_LAYOUT_ALIGNED 1
#define FONT_LAYOUT_LINE(line, width, count, bytes) \
(void)(line); \
line_x = ((int)VIDEO_XR_TAG_COLS - (int)(width)) / 2;
#define FONT_LAYOUT_GLYPH(glyph, pen_x, pen_y) \
openxr_frame_tag_glyph(drv->get_atlas(font), (glyph), \
line_x + (pen_x) + (glyph)->draw_offset_x, \
base - (pen_y) + (glyph)->draw_offset_y, cov);
#include "../font_layout.h"
}
/* Tag tag's words: the laser's, a screen's by number, the menu's. */
static void openxr_frame_tag_text(unsigned tag, char *s, size_t len)
{
if (!tag)
strlcpy(s, msg_hash_to_str(MSG_OPENXR_EDIT_TAG_LASER), len);
else if (tag == VIDEO_XR_MENU_SLOT + 1)
strlcpy(s, msg_hash_to_str(MSG_OPENXR_EDIT_TAG_MENU), len);
else
snprintf(s, len, msg_hash_to_str(MSG_OPENXR_EDIT_TAG_SCREEN), tag);
}
/* Edit mode's tags into px, in the default font. */
static void openxr_frame_cursor_tags(uint8_t *px, bool bottom_up)
{
unsigned i;
int asc, desc;
uint8_t *cov = NULL;
void *font = NULL;
const font_renderer_driver_t *drv = NULL;
unsigned size = VIDEO_XR_TAG_FONT;
if (!font_renderer_create_default(&drv, &font, NULL, size,
FONT_ATLAS_FORMAT_A8))
return;
openxr_frame_tag_metrics(drv, font, size, &asc, &desc);
/* A font whose line is taller than the rows is drawn smaller, so no
* descender is cut. */
if (asc + desc > VIDEO_XR_TAG_TEXT_ROWS)
{
if (drv->free)
drv->free(font);
font = NULL;
size = VIDEO_XR_TAG_FONT * VIDEO_XR_TAG_TEXT_ROWS / (asc + desc);
if (!size || !font_renderer_create_default(&drv, &font, NULL, size,
FONT_ATLAS_FORMAT_A8))
return;
openxr_frame_tag_metrics(drv, font, size, &asc, &desc);
}
if ((cov = (uint8_t*)malloc(VIDEO_XR_TAG_COLS * VIDEO_XR_TAG_ROWS)))
{
for (i = 0; i < VIDEO_XR_TAGS; i++)
{
char text[64];
openxr_frame_tag_text(i, text, sizeof(text));
memset(cov, 0, VIDEO_XR_TAG_COLS * VIDEO_XR_TAG_ROWS);
openxr_frame_tag_line(drv, font, asc, desc, text, cov);
video_xr_tag_pixels(px, i, cov, bottom_up);
}
free(cov);
}
if (drv->free)
drv->free(font);
}
void openxr_frame_cursor_pixels(uint8_t *px, unsigned w, bool bottom_up)
{
unsigned i;
video_xr_cursor_pixels(px);
if (w == VIDEO_XR_CURSOR_W)
video_xr_cut_pixels(px, bottom_up);
if (w >= VIDEO_XR_CURSOR_TAGGED_W)
openxr_frame_cursor_tags(px, bottom_up);
if (w < VIDEO_XR_CURSOR_W)
for (i = 1; i < VIDEO_XR_CURSOR_H; i++)
memmove(px + (size_t)i * w * 4,
px + (size_t)i * VIDEO_XR_CURSOR_W * 4, (size_t)w * 4);
}
+122 -7
View File
@@ -36,7 +36,59 @@
RETRO_BEGIN_DECLS
#define OPENXR_MAX_EXTRA_LAYERS 4
/* Layers the input may add to the screens: the laser's dots, kept
* where the screens must make room, then edit mode's outlines and the
* menu's handle, then its hinges' lines, each hand's, then up to
* OPENXR_REST_HINGES between screens at rest, then its tag, then the
* beams, only in room they leave. */
#define OPENXR_KEPT_EXTRA_LAYERS 2
/* The most edge to edge pairs eight equal screens make in a plane. */
#define OPENXR_REST_HINGES 10
#define OPENXR_HINGE_LAYERS (2 + OPENXR_REST_HINGES)
#define OPENXR_BEAM_LAYERS 2
#define OPENXR_TAG_LAYERS 1
#define OPENXR_MAX_EXTRA_LAYERS \
(OPENXR_KEPT_EXTRA_LAYERS + OPENXR_HINGE_LAYERS + OPENXR_BEAM_LAYERS \
+ OPENXR_TAG_LAYERS + VIDEO_XR_MAX_SLOTS + 1)
/* Edit mode's hands, published by the main thread for the frame loop. */
typedef struct openxr_grabs
{
video_xr_grab_t hand[2];
video_xr_pose_t start[2]; /* items as taken; snaps fade in */
/* Each slot's last let-go, until quads planned at it show it. */
video_xr_grab_t let_go[VIDEO_XR_MAX_SLOTS];
unsigned chosen; /* its slot plus one, 0 the laser's */
unsigned stuck; /* hands with the menu's handle stuck, bits */
unsigned tag; /* the hand that chose last (0, the left) */
unsigned num_rests; /* screens no hand holds resting in a hinge */
video_xr_rest_t rests[OPENXR_REST_HINGES];
bool snap; /* Snap to Screens */
bool editing; /* Edit Screens is on */
} openxr_grabs_t;
/* Edit Smoothing's aims, the frame loop's for the main thread: each
* hand's, in the frame's space, as the frame last built from it took
* it, and that frame's display time; bit h of valid while hand h's is
* tracked. */
typedef struct openxr_aims
{
video_xr_pose_t pose[2];
XrTime time[2];
unsigned valid;
} openxr_aims_t;
/* Where a hand's held item was last shown, for the hold (slot and
* state) it was shown for, and the hinge the main thread marks, if
* hinged, in the screens' frame. */
typedef struct openxr_held
{
video_xr_pose_t pose;
video_xr_hinge_t hinge;
unsigned slot;
unsigned state;
bool hinged;
} openxr_held_t;
/* What the XR thread publishes for the drawing thread. */
typedef struct openxr_tracked
@@ -92,9 +144,17 @@ typedef struct openxr_frame
const openxr_runtime_t *rt;
openxr_session_t *session;
openxr_swapchains_t *sc;
/* Layers after the screens (the laser, its cursor); may be NULL. */
/* Layers after the screens (the laser, its cursor), the first *under
* of them under the screens instead, shown holding this frame's quads
* where it shows them, in space; may be NULL. */
unsigned (*extra_layers)(void *user, XrTime display_time,
const XrCompositionLayerBaseHeader **layers, unsigned cap);
const video_xr_quad_set_t *shown, XrSpace space,
const XrCompositionLayerBaseHeader **layers, unsigned cap,
unsigned *under);
/* The input's: a hand's aim pose at a frame's time in space, for what
* it holds; may be NULL. */
bool (*aim)(void *user, unsigned hand, XrSpace space, XrTime time,
video_xr_pose_t *pose);
void *extra_user;
/* The hands; may be NULL. */
bool (*locate_hands)(void *user, XrSpace base, XrTime time,
@@ -122,6 +182,26 @@ typedef struct openxr_frame
openxr_screens_t screens; /* the frame loop's thread */
retro_atomic_int_t screens_seq;
retro_atomic_int_t screens_words[2 * OPENXR_WORDS(openxr_screens_t)];
/* Edit mode's hands as the main thread publishes them, the same way,
* and the frame loop's copy, read again only once the stamp it was
* read at moves. The main thread is the one writer. */
retro_atomic_int_t grabs_seq;
retro_atomic_int_t grabs_words[2 * OPENXR_WORDS(openxr_grabs_t)];
openxr_grabs_t grabs; /* the frame loop's */
int grabs_read; /* the frame loop's */
openxr_held_t held[2]; /* the frame loop's */
/* Each held hinge where this frame draws, bit h for hand h's. */
video_xr_hinge_t hinges[2]; /* the frame loop's */
unsigned hinged; /* the frame loop's */
/* Edit Smoothing's aims as the frame loop publishes them. */
retro_atomic_int_t aims_seq;
retro_atomic_int_t aims_words[2 * OPENXR_WORDS(openxr_aims_t)];
/* This frame's quads where it shows them, for the input's layers,
* and those it shows by their backs, a bit each. */
video_xr_quad_set_t shown; /* the frame loop's */
uint32_t backs; /* the frame loop's */
/* The menu's index among this frame's quad layers, or -1. */
int menu_layer; /* the frame loop's */
/* The drawing thread's quads for the XR thread and the pointer, the
* same way. */
retro_atomic_int_t quads_seq;
@@ -208,21 +288,31 @@ typedef struct openxr_input_hooks
void (*session_created)(void *user, const openxr_input_handles_t *h);
/* The frame loop has stopped; the session is destroyed next. */
void (*session_destroying)(void *user);
/* The frame loop's thread, each headset frame: up to cap layers over
* the screens. Returns how many were written. */
/* The frame loop's thread, each headset frame: up to cap layers with
* the screens, which shown holds where this frame shows them, in
* space; the first OPENXR_KEPT_EXTRA_LAYERS for the dots, the rest
* for what only fits in room the screens leave. Returns how many were
* written: the first *under go under the screens, the rest over. */
unsigned (*frame_layers)(void *user, XrTime display_time,
const XrCompositionLayerBaseHeader **layers, unsigned cap);
const video_xr_quad_set_t *shown, XrSpace space,
const XrCompositionLayerBaseHeader **layers, unsigned cap,
unsigned *under);
/* The two hands' poses and joints in base at time.
* False when none could be located. */
bool (*locate_hands)(void *user, XrSpace base, XrTime time,
struct retro_vr_controller_poses *out);
/* The frame loop's thread: a hand's aim pose at a frame's time in
* space; false while it is not tracked. */
bool (*aim)(void *user, unsigned hand, XrSpace space, XrTime time,
video_xr_pose_t *pose);
void *user;
} openxr_input_hooks_t;
bool openxr_frame_load(openxr_frame_t *f, const openxr_runtime_t *rt,
openxr_session_t *session, openxr_swapchains_t *sc);
void openxr_frame_deinit(openxr_frame_t *f);
/* A new session: the anchor straight ahead, the rates it offers. */
/* A new session: the anchor straight ahead, nothing held, the rates it
* offers. */
void openxr_frame_session_created(openxr_frame_t *f);
/* The desktop host: the XR thread. */
bool openxr_frame_start(openxr_frame_t *f);
@@ -262,6 +352,13 @@ void openxr_frame_request_rate(openxr_frame_t *f, float hz);
/* The drawing thread's quads, placed from an identity anchor: each
* headset frame puts them where the screens are. */
void openxr_frame_publish(openxr_frame_t *f, const video_xr_quad_set_t *set);
/* Main thread: edit mode's hands, for each frame to place what they
* hold at its own aim. */
void openxr_frame_publish_grabs(openxr_frame_t *f, const openxr_grabs_t *g);
/* The frame loop's thread: the hands' smoothed aims. */
void openxr_frame_publish_aims(openxr_frame_t *f, const openxr_aims_t *a);
/* The main thread: the aims last published. */
void openxr_frame_get_aims(openxr_frame_t *f, openxr_aims_t *out);
/* The projection the XR thread submits from now on, behind the
* screens; left out until its slot releases an image, and once the
* track space it was drawn in is replaced without a carry. Published
@@ -279,6 +376,10 @@ void openxr_frame_set_placement(openxr_frame_t *f, unsigned placement);
* showed them, and the space they are in: what the laser points at. */
void openxr_frame_get_shown_quads(openxr_frame_t *f,
video_xr_quad_set_t *out, XrSpace *space);
/* Any thread: where the last frame showed the screens (the anchor,
* VIEW's identity, or Lazy Follow's), and their space. */
void openxr_frame_get_screens(openxr_frame_t *f, video_xr_pose_t *pose,
XrSpace *space);
void openxr_frame_request_recenter(openxr_frame_t *f);
/* Any thread: how often the anchor has moved, and the display period
* the frames publish, 0 until measured. */
@@ -292,6 +393,20 @@ void openxr_frame_set_input_hooks(const openxr_input_hooks_t *hooks);
void openxr_frame_input_attach(openxr_frame_t *f);
void openxr_frame_input_detach(openxr_frame_t *f);
XrSwapchain openxr_frame_cursor(const openxr_frame_t *f);
/* The width hosts make the laser's image: VIDEO_XR_CURSOR_W; where the
* runtime's swapchains are narrower (4096 if it does not say),
* VIDEO_XR_CURSOR_TAGGED_W, without the cut outlines, or
* VIDEO_XR_CURSOR_UNTAGGED_W, without the tags too. */
unsigned openxr_frame_cursor_w(const openxr_frame_t *f);
/* What the image at that width has past the outlines, for the log. */
const char *openxr_frame_cursor_parts(const openxr_frame_t *f);
/* The laser's image (video_xr_cursor_pixels, video_xr_cut_pixels) w
* wide, as openxr_frame_cursor_w says, in px's VIDEO_XR_CURSOR_W by
* VIDEO_XR_CURSOR_H texels; from VIDEO_XR_CURSOR_TAGGED_W wide with
* edit mode's tags written in the default font and the interface
* language as it is now; bottom_up for an image whose rows run bottom
* up, as GL's. Without a font, the tags stay clear. */
void openxr_frame_cursor_pixels(uint8_t *px, unsigned w, bool bottom_up);
RETRO_END_DECLS
+2
View File
@@ -250,6 +250,7 @@ enum openxr_runtime_result openxr_runtime_find_system(openxr_runtime_t *rt)
}
rt->max_dim = 4096;
rt->max_width = 0;
rt->max_layers = XR_MIN_COMPOSITION_LAYERS_SUPPORTED;
props.type = XR_TYPE_SYSTEM_PROPERTIES;
props.next = NULL;
@@ -261,6 +262,7 @@ enum openxr_runtime_result openxr_runtime_find_system(openxr_runtime_t *rt)
{
uint32_t mw = props.graphicsProperties.maxSwapchainImageWidth;
uint32_t mh = props.graphicsProperties.maxSwapchainImageHeight;
rt->max_width = mw;
if (mw && mw < rt->max_dim)
rt->max_dim = mw;
if (mh && mh < rt->max_dim)
+1
View File
@@ -53,6 +53,7 @@ typedef struct openxr_runtime
XrSystemId system;
XrEnvironmentBlendMode blend_mode;
unsigned max_dim; /* the largest swapchain side */
unsigned max_width; /* the widest swapchain, 0 if not said */
uint32_t rec_width; /* one eye's recommended image width */
uint32_t rec_height; /* and its height */
uint32_t max_layers; /* the most layers a frame may end with */
+19 -8
View File
@@ -584,8 +584,8 @@ static bool vulkan_openxr_cursor_fill(void *user, uint32_t index,
VkCommandBuffer cmd = VK_NULL_HANDLE;
VkFence fence = VK_NULL_HANDLE;
void *map = NULL;
VkDeviceSize size = VIDEO_XR_CURSOR_DIM
* VIDEO_XR_CURSOR_DIM * 4;
VkDeviceSize size = (VkDeviceSize)VIDEO_XR_CURSOR_W
* VIDEO_XR_CURSOR_H * 4;
VkMemoryPropertyFlags host = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
VkBufferCreateInfo bi;
@@ -632,7 +632,8 @@ static bool vulkan_openxr_cursor_fill(void *user, uint32_t index,
failed = "staging memory";
goto end;
}
video_xr_cursor_pixels((uint8_t*)map);
openxr_frame_cursor_pixels((uint8_t*)map,
openxr_frame_cursor_w(&dot->xr->frame), false);
vkUnmapMemory(device, memory);
pi.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
@@ -690,8 +691,9 @@ static bool vulkan_openxr_cursor_fill(void *user, uint32_t index,
region.imageOffset.z = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = VIDEO_XR_CURSOR_DIM;
region.imageExtent.height = VIDEO_XR_CURSOR_DIM;
region.imageExtent.width =
openxr_frame_cursor_w(&dot->xr->frame);
region.imageExtent.height = VIDEO_XR_CURSOR_H;
region.imageExtent.depth = 1;
vkCmdCopyBufferToImage(cmd, buffer, dot->images[index][0],
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
@@ -760,11 +762,20 @@ static void vulkan_openxr_cursor_create(vulkan_openxr_t *xr,
li.xr = xr;
li.images = dot.images;
xr->frame.cursor = openxr_swapchains_create_filled(&xr->sc, formats, 4,
VIDEO_SCALE_PACK(VIDEO_XR_CURSOR_DIM, VIDEO_XR_CURSOR_DIM),
VIDEO_SCALE_PACK(openxr_frame_cursor_w(&xr->frame),
VIDEO_XR_CURSOR_H),
vulkan_openxr_list_images, &li, vulkan_openxr_cursor_fill, &dot,
&step, &code);
if (xr->frame.cursor)
RARCH_LOG("[OpenXR] Laser cursor ready.\n");
if (xr->frame.cursor && xr->frame.rt->max_width)
RARCH_LOG("[OpenXR] Laser cursor ready: %u wide, with %s (the "
"headset's widest %u).\n", openxr_frame_cursor_w(&xr->frame),
openxr_frame_cursor_parts(&xr->frame),
xr->frame.rt->max_width);
else if (xr->frame.cursor)
RARCH_LOG("[OpenXR] Laser cursor ready: %u wide, with %s (the "
"headset does not say its widest).\n",
openxr_frame_cursor_w(&xr->frame),
openxr_frame_cursor_parts(&xr->frame));
else
RARCH_WARN("[OpenXR] No laser cursor (%s, %d).\n", step, code);
}
+15 -6
View File
@@ -5153,8 +5153,8 @@ typedef struct gl2_desktop_xr
video_xr_quad_set_t set;
/* Where each slot's layers lie in the window, from its top left. */
video_views_rect_t rects[VIDEO_XR_MAX_SLOTS][2];
/* The slots the headset has an image for. */
bool published[VIDEO_XR_MAX_SLOTS];
/* The size of each slot's image the headset has, 0 for none. */
unsigned published[VIDEO_XR_MAX_SLOTS];
GLuint fbo;
/* The core's last frame size: a repeat brings none. */
unsigned frame_dims;
@@ -5228,6 +5228,7 @@ static void gl2_desktop_xr_plan(gl2_t *gl,
const video_frame_info_t *video_info, const video_viewport_t *vp,
bool map, bool eyes)
{
unsigned s;
video_xr_params_t p;
gl2_desktop_xr_t *xr = gl->desktop_xr;
bool menu = false;
@@ -5241,6 +5242,10 @@ static void gl2_desktop_xr_plan(gl2_t *gl,
p.map = map ? &video_info->views : NULL;
p.frame_dims = gl->video_dims;
video_xr_pose_identity(&p.anchor);
/* A window shown whole despite a map is not screen 1. */
p.screens = (map || !video_info->views.num_views)
? video_info->headset_screens : NULL;
p.edit_seq = video_info->headset_edit_seq;
p.distance = video_info->headset_distance;
p.width = video_info->headset_width;
p.px_per_rad = gl_openxr_pixels_per_radian(xr->host);
@@ -5254,6 +5259,9 @@ static void gl2_desktop_xr_plan(gl2_t *gl,
video_xr_place_window(&p, menu,
map && gl2_desktop_xr_tracked(video_info), vp, xr->frame_dims,
&xr->set, xr->rects);
for (s = 0; s < VIDEO_XR_MAX_SLOTS; s++)
xr->set.slots[s].dims = video_xr_keep_dims(xr->set.slots[s].dims,
xr->published[s], video_info->headset_editing);
}
/* The plan's rectangles out of the window's back buffer, which is
@@ -5280,7 +5288,7 @@ static void gl2_desktop_xr_draw(gl2_t *gl, bool eyes)
else
gl_openxr_slot_forget(xr->host, s);
}
xr->published[s] = false;
xr->published[s] = 0;
continue;
}
if ( gl_openxr_slot_refused(xr->host, s) == plan->dims
@@ -5304,7 +5312,7 @@ static void gl2_desktop_xr_draw(gl2_t *gl, bool eyes)
GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
gl_openxr_slot_publish(xr->host, s, &xr->set, NULL);
xr->published[s] = true;
xr->published[s] = plan->dims;
}
}
@@ -5330,7 +5338,7 @@ static bool gl2_desktop_xr_ready(gl2_t *gl)
{
if (xr->published[s])
gl_openxr_slot_forget(xr->host, s);
xr->published[s] = false;
xr->published[s] = 0;
}
if (xr->proj_shown)
gl_openxr_slot_forget(xr->host, OPENXR_PROJECTION_SLOT);
@@ -7370,7 +7378,8 @@ static bool gl2_frame(void *data, const void *frame,
? 0 : video_info->headset_interval, video_info->headset_request_hz);
gl_android_openxr_set_views(&video_info->views, dims, &xr_vp,
gl->rotation / 90, video_info->screen_layout,
video_info->stereo_swap_eyes);
video_info->stereo_swap_eyes, video_info->headset_screens,
video_info->headset_edit_seq, video_info->headset_editing);
if (gl_android_openxr_active())
{
bool new_frame = frame && !video_info->frame_repeat;
+40 -11
View File
@@ -3165,7 +3165,7 @@ static void gl3_views_build_chains(gl3_t *gl)
set->preset[i] = gl3_filter_chain_create_from_preset(
gl->views.preset, gl->video_info.smooth
? GLSLANG_FILTER_CHAIN_LINEAR
: GLSLANG_FILTER_CHAIN_NEAREST, true);
: GLSLANG_FILTER_CHAIN_NEAREST, true, false);
if (!set->preset[i])
RARCH_ERR("[GLCore] Failed to create view %u's preset: \"%s\".\n",
i, gl->views.preset);
@@ -3184,7 +3184,7 @@ static void gl3_views_build_chains(gl3_t *gl)
*chain = gl3_filter_chain_create_from_preset(gl->views.preset,
gl->video_info.smooth
? GLSLANG_FILTER_CHAIN_LINEAR
: GLSLANG_FILTER_CHAIN_NEAREST, false);
: GLSLANG_FILTER_CHAIN_NEAREST, false, true);
if (!*chain)
RARCH_ERR("[OpenXR] Failed to create the headset's preset: \"%s\".\n",
gl->views.preset);
@@ -3211,7 +3211,7 @@ static bool gl3_init_filter_chain_preset(gl3_t *gl, const char *shader_path)
gl->video_info.smooth
? GLSLANG_FILTER_CHAIN_LINEAR
: GLSLANG_FILTER_CHAIN_NEAREST,
(gl->flags & GL3_FLAG_HW_RENDER_ENABLE) != 0);
(gl->flags & GL3_FLAG_HW_RENDER_ENABLE) != 0, false);
if (!gl->filter_chain)
{
@@ -6520,6 +6520,7 @@ static void gl3_xr_plan(gl3_t *gl, const video_frame_info_t *video_info,
bool views, bool eyes, unsigned frame_dims, unsigned src_dims,
unsigned ui_dims)
{
unsigned s;
video_xr_params_t p;
gl_openxr_t *xr = gl->xr.host;
@@ -6530,6 +6531,13 @@ static void gl3_xr_plan(gl3_t *gl, const video_frame_info_t *video_info,
/ (float)VIDEO_SCALE_H(frame_dims);
p.frame_dims = src_dims;
video_xr_pose_identity(&p.anchor);
/* A frame shown whole despite a map is not screen 1. */
p.screens = (views || !video_info->views.num_views)
? video_info->headset_screens : NULL;
p.edit_seq = video_info->headset_edit_seq;
p.menu = video_info->headset_menu_placed
? &video_info->headset_menu : NULL;
p.menu_follow = video_info->headset_menu_follow;
p.distance = video_info->headset_distance;
p.width = video_info->headset_width;
p.px_per_rad = gl_openxr_pixels_per_radian(xr);
@@ -6542,6 +6550,11 @@ static void gl3_xr_plan(gl3_t *gl, const video_frame_info_t *video_info,
if (views && eyes)
p.skip_screens = gl3_xr_eye_screens(video_info);
video_xr_place(&p, &gl->xr.set);
for (s = 0; s < VIDEO_XR_MAX_SLOTS; s++)
gl->xr.set.slots[s].dims = video_xr_keep_dims(
gl->xr.set.slots[s].dims,
gl->xr.shown[s].published ? gl->xr.shown[s].info.dims : 0,
video_info->headset_editing);
}
/* Whether slot s's plan this frame is what it last published. */
@@ -6592,10 +6605,13 @@ static void gl3_xr_forget(gl3_t *gl, unsigned s, bool yield)
}
/* Each layer of a screen slot drawn whole into tex by its view's
* headset chain or the whole frame's: its passes at the image's size. */
* headset chain or the whole frame's: its passes at the image's size. A
* repeat - a screen moved, resized or not yet shown while no new frame
* came - replays the last frame's final pass, so feedback and history
* stay where that frame left them. */
static void gl3_xr_draw_screen(gl3_t *gl, unsigned s, const uint32_t *tex,
gl3_filter_chain_t **chains, gl3_filter_chain_t *frame_chain,
const struct gl3_filter_chain_texture *frame_tex)
const struct gl3_filter_chain_texture *frame_tex, bool new_frame)
{
unsigned l;
struct gl3_viewport vp;
@@ -6608,6 +6624,8 @@ static void gl3_xr_draw_screen(gl3_t *gl, unsigned s, const uint32_t *tex,
struct gl3_filter_chain_texture in;
int v = plan->view[l];
gl3_filter_chain_t *chain = (v >= 0) ? chains[v] : frame_chain;
bool replay = chain && !new_frame
&& gl3_filter_chain_replay_ready(chain);
if (v >= 0)
{
@@ -6619,7 +6637,7 @@ static void gl3_xr_draw_screen(gl3_t *gl, unsigned s, const uint32_t *tex,
}
else
in = *frame_tex;
if (chain)
if (chain && !replay)
{
gl3_filter_chain_set_input_texture(chain, &in);
gl3_filter_chain_build_offscreen_passes(chain, &vp);
@@ -6631,10 +6649,13 @@ static void gl3_xr_draw_screen(gl3_t *gl, unsigned s, const uint32_t *tex,
glViewport(0, 0, VIDEO_SCALE_W(plan->dims), VIDEO_SCALE_H(plan->dims));
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (chain)
if (replay)
gl3_filter_chain_build_viewport_pass_replay(chain, &vp, gl->xr.mvp);
else if (chain)
{
gl3_filter_chain_build_viewport_pass(chain, &vp, gl->xr.mvp);
gl3_filter_chain_end_frame(chain);
if (new_frame)
gl3_filter_chain_end_frame(chain);
}
}
}
@@ -6794,7 +6815,8 @@ static void gl3_xr_draw(gl3_t *gl, const video_frame_info_t *video_info,
if (s == VIDEO_XR_MENU_SLOT)
gl3_xr_copy_ui(gl, s, tex[0], ui_dims);
else
gl3_xr_draw_screen(gl, s, tex, chains, frame_chain, frame_tex);
gl3_xr_draw_screen(gl, s, tex, chains, frame_chain, frame_tex,
new_frame);
gl_openxr_slot_publish(xr, s, &gl->xr.set, NULL);
gl3_xr_keep(gl, s);
}
@@ -6947,6 +6969,7 @@ static bool gl3_frame(void *data, const void *frame,
bool views_direct = false;
bool views_in_place = false;
bool views_ui = false;
bool views_ui_menu = false;
unsigned ui_layer_dims = 0;
/* A software frame a preset may draw as views, written once they are
* known: straight into each view's texture if they need copies */
@@ -7626,13 +7649,19 @@ static bool gl3_frame(void *data, const void *frame,
/* A failed size stays latched until the layer changes size. */
if (frame && !ui_layer_dims)
gl3_vv_target_drop(gl, &gl->views.ui_target, &gl->views.ui);
views_ui_menu = (gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE) ? true : false;
#ifdef HAVE_GL3_XR
/* Edit mode moves the menu's quad: it stays up, empty. */
if (xr_draw && video_info->headset_editing)
views_ui_menu = true;
#endif
#ifdef HAVE_GFX_WIDGETS
views_ui = VIDEO_VIEWS_UI_WANTED(ui_layer_dims,
(gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE), msg, statistics_show,
views_ui_menu, msg, statistics_show,
widgets_active && gfx_widgets_visible(video_info))
#else
views_ui = VIDEO_VIEWS_UI_WANTED(ui_layer_dims,
(gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE), msg, statistics_show, false)
views_ui_menu, msg, statistics_show, false)
#endif
&& gl3_vv_target_ensure(gl, &gl->views.ui_target, &gl->views.ui,
ui_layer_dims);
+60 -13
View File
@@ -6613,6 +6613,7 @@ static void vulkan_filter_chain_info_init(vk_t *vk,
info->swapchain.render_pass = vk->render_pass;
info->swapchain.num_indices = vk->context->num_swapchain_images;
info->frame_rect = false;
info->replay = false;
}
static bool vulkan_chain_emits_hdr10(vk_t *vk, vulkan_filter_chain_t *chain)
@@ -11262,6 +11263,7 @@ static void vulkan_xr_plan(vk_t *vk, const video_frame_info_t *video_info,
bool views, bool eyes, unsigned frame_dims, unsigned src_dims,
unsigned ui_dims)
{
unsigned s;
video_xr_params_t p;
vulkan_openxr_t *xr = vk->context->xr;
@@ -11272,6 +11274,13 @@ static void vulkan_xr_plan(vk_t *vk, const video_frame_info_t *video_info,
/ (float)VIDEO_SCALE_H(frame_dims);
p.frame_dims = src_dims;
video_xr_pose_identity(&p.anchor);
/* A frame shown whole despite a map is not screen 1. */
p.screens = (views || !video_info->views.num_views)
? video_info->headset_screens : NULL;
p.edit_seq = video_info->headset_edit_seq;
p.menu = video_info->headset_menu_placed
? &video_info->headset_menu : NULL;
p.menu_follow = video_info->headset_menu_follow;
p.distance = video_info->headset_distance;
p.width = video_info->headset_width;
p.px_per_rad = vulkan_openxr_pixels_per_radian(xr);
@@ -11284,6 +11293,11 @@ static void vulkan_xr_plan(vk_t *vk, const video_frame_info_t *video_info,
if (views && eyes)
p.skip_screens = vulkan_xr_eye_screens(video_info);
video_xr_place(&p, &vk->xr.set);
for (s = 0; s < VIDEO_XR_MAX_SLOTS; s++)
vk->xr.set.slots[s].dims = video_xr_keep_dims(
vk->xr.set.slots[s].dims,
vk->xr.slots[s].num_images ? vk->xr.slots[s].dims : 0,
video_info->headset_editing);
}
/* The headset's chain for a view, or for the whole frame when view is
@@ -11317,10 +11331,14 @@ static vulkan_filter_chain_t *vulkan_xr_chain(vk_t *vk,
return *chain;
}
/* Each layer of slot s's acquired image, drawn whole by its view's
* headset chain or the whole frame's: its passes at the image's size. */
/* Slot s's layers, each through its view's headset chain or the whole
* frame's, straight into the runtime's acquired images. A repeat - a
* screen resized, made again or not yet shown while no new frame came -
* replays the last frame's final pass at the image's size, so feedback
* and history stay where that frame left them. */
static void vulkan_xr_draw_screen(vk_t *vk, unsigned s,
vulkan_filter_chain_t **xr_chains, vulkan_filter_chain_t *xr_frame)
vulkan_filter_chain_t **xr_chains, vulkan_filter_chain_t *xr_frame,
bool new_frame)
{
unsigned l;
VkViewport vp;
@@ -11356,7 +11374,11 @@ static void vulkan_xr_draw_screen(vk_t *vk, unsigned s,
vulkan_filter_chain_t *chain = (plan->view[l] >= 0)
? xr_chains[plan->view[l]] : xr_frame;
if (chain)
bool replay = false;
if (chain && !new_frame)
replay = vulkan_filter_chain_replay_ready(chain);
if (chain && !replay)
vulkan_filter_chain_build_offscreen_passes(chain, vk->cmd, &vp);
rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
rp.pNext = NULL;
@@ -11371,11 +11393,14 @@ static void vulkan_xr_draw_screen(vk_t *vk, unsigned s,
vkCmdBeginRenderPass(vk->cmd, &rp, VK_SUBPASS_CONTENTS_INLINE);
vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT;
vk->tracker.pipeline = VK_NULL_HANDLE;
if (chain)
if (replay)
vulkan_filter_chain_build_viewport_pass_replay(chain, vk->cmd,
&vp, vk->xr.mvp.data);
else if (chain)
vulkan_filter_chain_build_viewport_pass(chain, vk->cmd,
&vp, vk->xr.mvp.data);
vkCmdEndRenderPass(vk->cmd);
if (chain)
if (chain && new_frame)
vulkan_filter_chain_end_frame(chain, vk->cmd);
}
@@ -11610,7 +11635,8 @@ static void vulkan_xr_draw(vk_t *vk, const video_frame_info_t *video_info,
unsigned frame_dims, unsigned src_dims, unsigned ui_dims,
bool window_drawn)
{
unsigned s;
unsigned s, q;
unsigned omit = 0;
vulkan_openxr_t *xr = vk->context->xr;
if (window_drawn)
@@ -11632,10 +11658,18 @@ static void vulkan_xr_draw(vk_t *vk, const video_frame_info_t *video_info,
vulkan_openxr_slot_forget(xr, s);
vk->xr.slots[s].released = false;
}
if ( !vk->xr.set.slots[s].dims
|| !vulkan_xr_slot_ensure(vk, s, vk->xr.set.slots[s].dims,
vk->xr.set.slots[s].layers))
if (!vk->xr.set.slots[s].dims)
continue;
if (!vulkan_xr_slot_ensure(vk, s, vk->xr.set.slots[s].dims,
vk->xr.set.slots[s].layers))
{
/* A slot the runtime left with no swapchain has nothing to
* show; one still holding its last size keeps the quad. */
if (!vk->xr.slots[s].num_images)
omit |= 1u << s;
continue;
}
/* A swapchain made again for a new size is not released. */
if ( s != VIDEO_XR_MENU_SLOT && !new_frame
&& vk->xr.slots[s].released)
continue;
@@ -11648,15 +11682,21 @@ static void vulkan_xr_draw(vk_t *vk, const video_frame_info_t *video_info,
#ifdef HAVE_THREADS
slock_unlock(vk->context->queue_lock);
#endif
/* The quad stays: the headset shows the slot's last image, or
* the swapchains a resize keeps until these have one. */
if (!ok)
continue;
/* The menu slot has no view: -1 would draw the whole frame. */
if (s == VIDEO_XR_MENU_SLOT)
vulkan_xr_copy_ui(vk, s, ui_dims);
else
vulkan_xr_draw_screen(vk, s, xr_chains, xr_frame);
vulkan_xr_draw_screen(vk, s, xr_chains, xr_frame, new_frame);
vk->xr.slots[s].drawn = true;
}
for (s = 0, q = 0; q < vk->xr.set.num_quads; q++)
if (!(omit & (1u << vk->xr.set.quads[q].slot)))
vk->xr.set.quads[s++] = vk->xr.set.quads[q];
vk->xr.set.num_quads = s;
/* A repeat draws the eyes only for a projection not shown yet. */
if (eyes && (new_frame || !vk->xr.proj_shown))
vulkan_xr_draw_projection(vk, video_info);
@@ -11958,6 +11998,7 @@ static bool vulkan_frame(void *data, const void *frame,
bool views_blend = false;
bool views_per_eye = false;
bool views_ui = false;
bool views_ui_menu = false;
unsigned ui_layer_dims = 0;
#ifdef HAVE_OPENXR
vulkan_filter_chain_t *xr_chains[RETRO_VIDEO_VIEWS_MAX];
@@ -12903,13 +12944,19 @@ static bool vulkan_frame(void *data, const void *frame,
/* The UI layer is drawn and composited only when there is UI, by
* the same test the HDR composite makes. Overlays are hidden per
* eye, and drawn around the layer when it is for a headset. */
views_ui_menu = (vk->flags & VK_FLAG_MENU_ENABLE) ? true : false;
#ifdef HAVE_OPENXR
/* Edit mode moves the menu's quad: it stays up, empty. */
if (xr_draw && video_info->headset_editing)
views_ui_menu = true;
#endif
#ifdef HAVE_GFX_WIDGETS
views_ui = VIDEO_VIEWS_UI_WANTED(ui_layer_dims,
(vk->flags & VK_FLAG_MENU_ENABLE), msg, statistics_show,
views_ui_menu, msg, statistics_show,
widgets_active && gfx_widgets_visible(video_info))
#else
views_ui = VIDEO_VIEWS_UI_WANTED(ui_layer_dims,
(vk->flags & VK_FLAG_MENU_ENABLE), msg, statistics_show, false)
views_ui_menu, msg, statistics_show, false)
#endif
&& vk->pipelines.alpha_premult
&& vulkan_vv_target_ensure(vk, &vk->views.ui_target, &vk->views.ui,
+8 -4
View File
@@ -27,9 +27,11 @@ static INLINE void gl_android_openxr_set_pacing(unsigned interval,
float hz) { (void)interval; (void)hz; }
static INLINE void gl_android_openxr_set_views(const video_views_map_t *map,
unsigned frame_dims, const video_viewport_t *vp, unsigned rotation,
unsigned screen_layout, bool swap_eyes)
unsigned screen_layout, bool swap_eyes,
const video_screen_xr_t *screens, unsigned edit_seq, bool editing)
{ (void)map; (void)frame_dims; (void)vp; (void)rotation;
(void)screen_layout; (void)swap_eyes; }
(void)screen_layout; (void)swap_eyes; (void)screens;
(void)edit_seq; (void)editing; }
static INLINE float gl_android_openxr_refresh(float *rates, unsigned cap,
unsigned *count) { (void)rates; (void)cap; *count = 0; return 0.0f; }
static INLINE void gl_android_openxr_new_frame(void) { }
@@ -76,11 +78,13 @@ void gl_android_openxr_set_placement(unsigned placement, float distance,
void gl_android_openxr_set_pacing(unsigned interval, float hz);
/* gl2, once a frame: the frame's views and the size they are in, where
* the window draws the frame (from its top left) and its quarter turns,
* and how screens are laid out and eyes swapped. While the menu is
* and how screens are laid out and eyes swapped. Screen adjustments,
* edit mode and its count are copied for this frame. While the menu is
* closed, an untracked map's screens are cut from the window. */
void gl_android_openxr_set_views(const video_views_map_t *map,
unsigned frame_dims, const video_viewport_t *vp, unsigned rotation,
unsigned screen_layout, bool swap_eyes);
unsigned screen_layout, bool swap_eyes,
const video_screen_xr_t *screens, unsigned edit_seq, bool editing);
/* The headset's measured rate, 0 until known, and up to cap of the
* rates it can be asked for. */
float gl_android_openxr_refresh(float *rates, unsigned cap,
+52 -14
View File
@@ -114,7 +114,9 @@ typedef struct gl_android_openxr
* has the frame: gl2 sets them in its frame, before the swap that
* draws with them, both on the video thread. */
video_views_map_t views;
video_screen_xr_t screens[RETRO_VIDEO_VIEWS_MAX];
video_viewport_t views_vp;
unsigned edit_seq;
unsigned views_dims;
unsigned rotation;
unsigned screen_layout;
@@ -122,6 +124,7 @@ typedef struct gl_android_openxr
/* An sRGB swapchain filled with GL_FRAMEBUFFER_SRGB_EXT off around
* the copy (EXT_sRGB_write_control). */
bool srgb_copy;
bool editing;
EGLDisplay egl_display;
EGLConfig egl_config;
@@ -138,6 +141,7 @@ static gl_android_openxr_t gl_xr;
static bool gl_xr_egl_init(gl_android_openxr_t *xr)
{
const char *ext;
EGLint num_configs = 0;
EGLint major = 0, minor = 0;
@@ -172,7 +176,7 @@ static bool gl_xr_egl_init(gl_android_openxr_t *xr)
return false;
}
const char *ext = eglQueryString(xr->egl_display, EGL_EXTENSIONS);
ext = eglQueryString(xr->egl_display, EGL_EXTENSIONS);
if (ext && strstr(ext, "EGL_KHR_surfaceless_context"))
RARCH_DBG("[XR] EGL_KHR_surfaceless_context supported\n");
@@ -371,17 +375,31 @@ static void gl_android_openxr_host_register(gl_android_openxr_t *xr)
/* ===================== the session ===================== */
struct gl_xr_dot
{
uint32_t tex[OPENXR_MAX_IMAGES][2];
unsigned w;
};
/* The dot's image, with the video context current. */
static bool gl_xr_cursor_fill(void *user, uint32_t index,
const char **step, int *code)
{
static uint8_t px[VIDEO_XR_CURSOR_DIM * VIDEO_XR_CURSOR_DIM * 4];
uint32_t (*tex)[2] = (uint32_t (*)[2])user;
video_xr_cursor_pixels(px);
glBindTexture(GL_TEXTURE_2D, (GLuint)tex[index][0]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_XR_CURSOR_DIM,
VIDEO_XR_CURSOR_DIM, GL_RGBA, GL_UNSIGNED_BYTE, px);
struct gl_xr_dot *dot = (struct gl_xr_dot*)user;
uint8_t *px = (uint8_t*)malloc(
(size_t)VIDEO_XR_CURSOR_W * VIDEO_XR_CURSOR_H * 4);
if (!px)
{
*step = "malloc";
*code = 0;
return false;
}
openxr_frame_cursor_pixels(px, dot->w, true);
glBindTexture(GL_TEXTURE_2D, (GLuint)dot->tex[index][0]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, dot->w, VIDEO_XR_CURSOR_H,
GL_RGBA, GL_UNSIGNED_BYTE, px);
glBindTexture(GL_TEXTURE_2D, 0);
free(px);
/* The compositor reads the image on its own context. */
glFlush();
return true;
@@ -392,19 +410,27 @@ static void gl_xr_cursor_create(gl_android_openxr_t *xr)
{
static const int64_t formats[2] = {
OPENXR_GLES_SRGB8_ALPHA8, OPENXR_GLES_RGBA8 };
uint32_t tex[OPENXR_MAX_IMAGES][2];
struct gl_xr_dot dot;
openxr_gles_images_t li;
const char *step = NULL;
int code = 0;
dot.w = openxr_frame_cursor_w(&xr->frame);
li.sc = &xr->sc;
li.tex = tex;
li.tex = dot.tex;
xr->frame.cursor = openxr_swapchains_create_filled(&xr->sc, formats, 2,
VIDEO_SCALE_PACK(VIDEO_XR_CURSOR_DIM, VIDEO_XR_CURSOR_DIM),
openxr_binding_gles_images, &li, gl_xr_cursor_fill, tex,
VIDEO_SCALE_PACK(dot.w, VIDEO_XR_CURSOR_H),
openxr_binding_gles_images, &li, gl_xr_cursor_fill, &dot,
&step, &code);
if (xr->frame.cursor)
RARCH_LOG("[XR] Laser cursor ready.\n");
if (xr->frame.cursor && xr->frame.rt->max_width)
RARCH_LOG("[XR] Laser cursor ready: %u wide, with %s (the "
"headset's widest %u).\n", dot.w,
openxr_frame_cursor_parts(&xr->frame),
xr->frame.rt->max_width);
else if (xr->frame.cursor)
RARCH_LOG("[XR] Laser cursor ready: %u wide, with %s (the "
"headset does not say its widest).\n", dot.w,
openxr_frame_cursor_parts(&xr->frame));
else
RARCH_WARN("[XR] No laser cursor (%s, %d).\n", step, code);
}
@@ -601,6 +627,8 @@ static void gl_xr_draw(gl_android_openxr_t *xr)
p.map = &xr->views;
p.frame_dims = xr->dims;
video_xr_pose_identity(&p.anchor);
p.screens = xr->screens;
p.edit_seq = xr->edit_seq;
p.distance = xr->distance;
p.width = xr->width;
p.px_per_rad = openxr_frame_pixels_per_radian(&xr->frame);
@@ -613,6 +641,9 @@ static void gl_xr_draw(gl_android_openxr_t *xr)
/* With no width, an empty set: the eyes are all there is. */
video_xr_place_window(&p, menu, stereo, &xr->views_vp, xr->views_dims,
&xr->set, rects);
for (s = 0; s < VIDEO_XR_MAX_SLOTS; s++)
xr->set.slots[s].dims = video_xr_keep_dims(xr->set.slots[s].dims,
xr->num_images[s] ? xr->slot_dims[s] : 0, xr->editing);
/* The blits are scissored: whatever gl2 drew last must not clip them. */
glDisable(GL_SCISSOR_TEST);
/* The copy keeps the stored values; the compositor decodes them.
@@ -691,10 +722,17 @@ void gl_android_openxr_set_placement(unsigned placement, float distance,
void gl_android_openxr_set_views(const video_views_map_t *map,
unsigned frame_dims, const video_viewport_t *vp, unsigned rotation,
unsigned screen_layout, bool swap_eyes)
unsigned screen_layout, bool swap_eyes,
const video_screen_xr_t *screens, unsigned edit_seq, bool editing)
{
gl_android_openxr_t *xr = &gl_xr;
xr->views = *map;
if (screens)
memcpy(xr->screens, screens, sizeof(xr->screens));
else
video_views_xr_reset(xr->screens, RETRO_VIDEO_VIEWS_MAX);
xr->edit_seq = edit_seq;
xr->editing = editing;
xr->views_dims = frame_dims;
xr->views_vp = *vp;
xr->rotation = rotation;
+57 -6
View File
@@ -3037,9 +3037,18 @@ struct gl3_filter_chain
struct gl3_framebuffer *copy_framebuffer;
gl3_common_resources common;
gl3_filter_chain_texture input_texture;
/* The count the last frame's final pass was drawn with. */
uint64_t drawn_frame_count;
/* num_original_history framebuffers; one more than the shaders read
* for a replaying chain, so the frame's input goes where the last
* frame did not. */
struct gl3_framebuffer **original_history;
size_t num_original_history;
bool require_clear;
/* A frame's final pass was drawn since the passes were built. */
bool drawn;
/* See gl3_filter_chain_create_from_preset. */
bool replay;
bool alias_initialized;
/* Every pass reads a frame where it lies: none is copied out. */
bool frame_in_place;
@@ -3165,7 +3174,7 @@ static void gl3_chain_update_history_info(struct gl3_filter_chain *chain)
{
unsigned i;
for (i = 0; i < chain->num_original_history; i++)
for (i = 0; i < chain->common.num_original_history; i++)
{
gl3_texture_t *source = (gl3_texture_t*)
&chain->common.original_history[i];
@@ -3331,12 +3340,32 @@ static void gl3_chain_build_viewport_pass(struct gl3_filter_chain *chain, const
/* For feedback FBOs, swap current and previous. */
if (!again)
{
for (i = 0; i < chain->num_passes; i++)
{
struct gl3_framebuffer *fb = gl3_pass_get_feedback_framebuffer(chain->passes[i]);
if (fb)
gl3_pass_end_frame(chain->passes[i]);
}
chain->drawn_frame_count =
chain->passes[chain->num_passes - 1]->frame_count;
chain->drawn = true;
}
}
/* The last frame's final pass in a later frame, at vp: what it read
* then - the passes' outputs and feedback, the history it saw - and its
* count. Nothing else runs; feedback does not swap, history stays. */
static void gl3_chain_replay_viewport_pass(struct gl3_filter_chain *chain,
const gl3_viewport vp, const float *mvp)
{
uint64_t count;
struct gl3_pass *pass = chain->passes[chain->num_passes - 1];
count = pass->frame_count;
pass->frame_count = chain->drawn_frame_count;
gl3_chain_build_viewport_pass(chain, vp, mvp, true);
pass->frame_count = count;
}
static bool gl3_chain_init_history(struct gl3_filter_chain *chain)
@@ -3371,14 +3400,17 @@ static bool gl3_chain_init_history(struct gl3_filter_chain *chain)
/* We don't need to store array element #0,
* since it's aliased with the actual original. */
required_images--;
if (!gl3_texture_array_resize(&chain->common.original_history,
&chain->common.num_original_history, required_images))
return false;
/* And one more to replay: the last frame's history stays. */
if (chain->replay)
required_images++;
chain->original_history = (struct gl3_framebuffer**)
calloc(required_images, sizeof(*chain->original_history));
if (!chain->original_history)
return false;
chain->num_original_history = required_images;
if (!gl3_texture_array_resize(&chain->common.original_history,
&chain->common.num_original_history, required_images))
return false;
for (i = 0; i < required_images; i++)
{
@@ -3387,7 +3419,8 @@ static bool gl3_chain_init_history(struct gl3_filter_chain *chain)
return false;
}
RARCH_LOG("[GLCore] Using history of %u frames.\n", (unsigned)(required_images));
RARCH_LOG("[GLCore] Using history of %u frames.\n",
(unsigned)chain->common.num_original_history);
/* On first frame, we need to clear the textures to
* a known state, but we need
@@ -3699,6 +3732,8 @@ static bool gl3_chain_init(struct gl3_filter_chain *chain)
if (!gl3_texture_array_resize(&chain->common.pass_outputs,
&chain->common.num_pass_outputs, chain->num_passes))
return false;
/* A rebuild remakes what a replay reads. */
chain->drawn = false;
gl3_chain_init_frame_in_place(chain);
return true;
}
@@ -4010,6 +4045,8 @@ static bool gl3_chain_finalize(struct gl3_filter_chain *chain)
if (!gl3_texture_array_resize(&chain->common.pass_outputs,
&chain->common.num_pass_outputs, chain->num_passes))
return false;
/* A rebuild remakes what a replay reads. */
chain->drawn = false;
gl3_chain_init_frame_in_place(chain);
return true;
}
@@ -4372,7 +4409,8 @@ gl3_filter_chain_t *gl3_filter_chain_create_shrink(void)
}
gl3_filter_chain_t *gl3_filter_chain_create_from_preset(
const char *path, glslang_filter_chain_filter filter, bool frame_rect)
const char *path, glslang_filter_chain_filter filter, bool frame_rect,
bool replay)
{
size_t j;
unsigned i;
@@ -4412,6 +4450,7 @@ gl3_filter_chain_t *gl3_filter_chain_create_from_preset(
}
chain->common.sim_source_dims = shader->sim_source_dims;
chain->common.frame_rect = frame_rect;
chain->replay = replay;
chain->last_pass = last_pass;
if ( shader->luts
@@ -5041,6 +5080,18 @@ void gl3_filter_chain_build_viewport_pass_again(
gl3_chain_build_viewport_pass(chain, *vp, mvp, true);
}
bool gl3_filter_chain_replay_ready(const gl3_filter_chain_t *chain)
{
return chain->drawn;
}
void gl3_filter_chain_build_viewport_pass_replay(
gl3_filter_chain_t *chain,
const gl3_viewport *vp, const float *mvp)
{
gl3_chain_replay_viewport_pass(chain, *vp, mvp);
}
void gl3_filter_chain_end_frame(gl3_filter_chain_t *chain)
{
gl3_chain_end_frame(chain);
+15 -2
View File
@@ -206,11 +206,13 @@ gl3_filter_chain_t *gl3_filter_chain_create_shrink(void);
/* @frame_rect: frames may come inside a larger texture (padded_dims);
* the passes are built to read them there where the preset allows,
* rather than have each copied out. */
* rather than have each copied out. @replay: the chain may draw its last
* frame's final pass again in a later frame (build_viewport_pass_replay),
* and its history keeps one more texture for that. */
gl3_filter_chain_t *gl3_filter_chain_create_from_preset(
const char *path,
enum glslang_filter_chain_filter filter,
bool frame_rect);
bool frame_rect, bool replay);
/**
* Deferred (per-frame) filter chain creation API.
@@ -259,6 +261,17 @@ bool gl3_filter_chain_compile_pass(
**/
bool gl3_filter_chain_finalize(gl3_filter_chain_t *chain);
/* Whether a frame's final pass was drawn since the passes were built,
* for build_viewport_pass_replay to draw again. */
bool gl3_filter_chain_replay_ready(const gl3_filter_chain_t *chain);
/* The last frame's final pass, in a later frame and at vp, from what it
* read then, in place of the offscreen and viewport passes: no other
* pass runs, the feedback buffers do not swap and the history stays.
* For a frame shown again, resized or redrawn. */
void gl3_filter_chain_build_viewport_pass_replay(
gl3_filter_chain_t *chain,
const struct gl3_viewport *vp,
const float *mvp);
void gl3_filter_chain_end_frame(gl3_filter_chain_t *chain);
GLuint gl3_cross_compile_program(
+74 -6
View File
@@ -982,10 +982,18 @@ struct vulkan_filter_chain
vulkan_filter_chain_swapchain_info swapchain_info;
unsigned current_sync_index;
/* The count the last frame's final pass was drawn with. */
uint64_t drawn_frame_count;
/* num_history slots; one more than the shaders read for a replaying
* chain, so the frame's input goes where the last frame did not. */
struct slang_framebuffer **original_history;
size_t num_history;
unsigned history_ring_index;
bool require_clear;
/* A frame's final pass was drawn since the passes were built. */
bool drawn;
/* See vulkan_filter_chain_create_info. */
bool replay;
bool alias_initialized;
bool emits_hdr_colorspace;
bool emits_hdr16_output;
@@ -1620,6 +1628,7 @@ static struct vulkan_filter_chain *slang_chain_new(
chain->max_input_size_dims = info->max_input_dims;
chain->deferred_source_dims = chain->max_input_size_dims;
chain->common.frame_rect = info->frame_rect;
chain->replay = info->replay;
if ( !slang_chain_set_swapchain_info(chain, info->swapchain)
|| !slang_chain_set_num_passes(chain, info->num_passes))
{
@@ -1813,7 +1822,7 @@ static void slang_chain_update_history_info(struct vulkan_filter_chain *chain)
unsigned i;
unsigned hist_size = (unsigned)chain->num_history;
for (i = 0; i < hist_size; i++)
for (i = 0; i < chain->common.num_original_history; i++)
{
/* Map logical index i (0 = most recent) to the ring buffer slot. */
unsigned ring_slot = (chain->history_ring_index + i) % hist_size;
@@ -2036,8 +2045,46 @@ static void slang_chain_build_viewport_pass(struct vulkan_filter_chain *chain,
/* For feedback FBOs, swap current and previous. */
if (!again)
{
for (i = 0; i < chain->pass_count; i++)
slang_pass_end_frame(chain->passes[i]);
chain->drawn_frame_count =
chain->passes[chain->pass_count - 1]->frame_count;
chain->drawn = true;
}
}
/* The last frame's final pass in a later frame, at vp: what it read
* then - the passes' outputs and feedback, the history it saw - and its
* count. Nothing else runs; feedback does not swap, history stays. */
static void slang_chain_replay_viewport_pass(
struct vulkan_filter_chain *chain, VkCommandBuffer cmd,
const VkViewport vp, const float *mvp)
{
Texture source;
Texture original;
uint64_t count;
struct slang_pass *pass = chain->passes[chain->pass_count - 1];
struct deferred_disposes *disposer = &chain->deferred_calls[chain->current_sync_index];
original.texture = chain->input_texture;
original.filter = chain->passes[0]->pass_info.source_filter;
original.mip_filter = chain->passes[0]->pass_info.mip_filter;
original.address = chain->passes[0]->pass_info.address;
if (chain->pass_count == 1)
{
source = original;
source.filter = pass->pass_info.source_filter;
source.mip_filter = pass->pass_info.mip_filter;
source.address = pass->pass_info.address;
}
else
source = chain->common.pass_outputs[chain->pass_count - 2];
count = pass->frame_count;
pass->frame_count = chain->drawn_frame_count;
slang_pass_build_commands(pass, disposer, cmd, &original, &source,
&vp, mvp, false);
pass->frame_count = count;
}
static bool slang_chain_init_history(struct vulkan_filter_chain *chain)
@@ -2063,7 +2110,8 @@ static bool slang_chain_init_history(struct vulkan_filter_chain *chain)
* swapchain. The num_history test is load-bearing, not redundant:
* without it a fresh chain with required_images == 1 would take this
* return and skip the common.original_history reset below. */
if (chain->num_history && chain->num_history + 1 == required_images)
if ( chain->num_history
&& chain->num_history + (chain->replay ? 0 : 1) == required_images)
return true;
for (i = 0; i < chain->num_history; i++)
@@ -2086,12 +2134,15 @@ static bool slang_chain_init_history(struct vulkan_filter_chain *chain)
/* We don't need to store array element #0,
* since it's aliased with the actual original. */
required_images--;
if (!(chain->original_history = (struct slang_framebuffer**)
calloc(required_images, sizeof(*chain->original_history))))
return false;
if (!texture_array_resize(&chain->common.original_history,
&chain->common.num_original_history, required_images))
return false;
/* And one more to replay: the last frame's history stays. */
if (chain->replay)
required_images++;
if (!(chain->original_history = (struct slang_framebuffer**)
calloc(required_images, sizeof(*chain->original_history))))
return false;
for (i = 0; i < required_images; i++)
{
@@ -2106,7 +2157,8 @@ static bool slang_chain_init_history(struct vulkan_filter_chain *chain)
}
#ifdef VULKAN_DEBUG
RARCH_LOG("[Vulkan] Using history of %u frames.\n", (unsigned)(required_images));
RARCH_LOG("[Vulkan] Using history of %u frames.\n",
(unsigned)chain->common.num_original_history);
#endif
/* On first frame, we need to clear the textures to
@@ -2436,6 +2488,8 @@ static bool slang_chain_init(struct vulkan_filter_chain *chain)
RARCH_DBG("[Vulkan] Chain feedback ready.\n");
texture_array_resize(&chain->common.pass_outputs,
&chain->common.num_pass_outputs, chain->pass_count);
/* A rebuild remakes what a replay reads. */
chain->drawn = false;
slang_chain_init_frame_in_place(chain);
return true;
}
@@ -2776,6 +2830,8 @@ static bool slang_chain_finalize(struct vulkan_filter_chain *chain)
return false;
texture_array_resize(&chain->common.pass_outputs,
&chain->common.num_pass_outputs, chain->pass_count);
/* A rebuild remakes what a replay reads. */
chain->drawn = false;
slang_chain_init_frame_in_place(chain);
return true;
}
@@ -5756,6 +5812,18 @@ void vulkan_filter_chain_build_viewport_pass_again(
slang_chain_build_viewport_pass(chain, cmd, *vp, mvp, true);
}
bool vulkan_filter_chain_replay_ready(const vulkan_filter_chain_t *chain)
{
return chain->drawn;
}
void vulkan_filter_chain_build_viewport_pass_replay(
vulkan_filter_chain_t *chain,
VkCommandBuffer cmd, const VkViewport *vp, const float *mvp)
{
slang_chain_replay_viewport_pass(chain, cmd, *vp, mvp);
}
void vulkan_filter_chain_end_frame(
vulkan_filter_chain_t *chain,
VkCommandBuffer cmd)
+14
View File
@@ -112,6 +112,10 @@ struct vulkan_filter_chain_create_info
/* Frames may come inside a larger image (padded_dims, origin): the
* passes are built to read them there where the preset allows. */
bool frame_rect;
/* The chain may draw its last frame's final pass again in a later
* frame (build_viewport_pass_replay): its history keeps one more
* image for that. */
bool replay;
#ifdef VULKAN_HDR_SWAPCHAIN
bool hdr_enabled;
#endif /* VULKAN_HDR_SWAPCHAIN */
@@ -232,6 +236,16 @@ void vulkan_filter_chain_build_viewport_pass(vulkan_filter_chain_t *chain,
void vulkan_filter_chain_build_viewport_pass_again(
vulkan_filter_chain_t *chain,
VkCommandBuffer cmd, const VkViewport *vp, const float *mvp);
/* Whether a frame's final pass was drawn since the passes were built,
* for build_viewport_pass_replay to draw again. */
bool vulkan_filter_chain_replay_ready(const vulkan_filter_chain_t *chain);
/* The last frame's final pass, in a later frame and at vp, from what it
* read then, in place of the offscreen and viewport passes: no other
* pass runs, the feedback buffers do not swap and the history stays.
* For a frame shown again, resized or redrawn. */
void vulkan_filter_chain_build_viewport_pass_replay(
vulkan_filter_chain_t *chain,
VkCommandBuffer cmd, const VkViewport *vp, const float *mvp);
void vulkan_filter_chain_end_frame(vulkan_filter_chain_t *chain,
VkCommandBuffer cmd);
+45
View File
@@ -4165,6 +4165,12 @@ unsigned video_driver_headset_view_toggle(void)
return VIDEO_HEADSET_VIEW_LAST;
}
void video_driver_headset_edit(bool editing, unsigned seq)
{
video_driver_st.headset_editing = editing;
video_driver_st.headset_edit_seq = seq;
}
void video_driver_headset_exit_request(void)
{
retro_atomic_store_release_int(&video_driver_st.headset_exit, 1);
@@ -6839,6 +6845,39 @@ void video_driver_ff_frameskip_decide(retro_time_t now)
video_st->ff_last_time = now;
}
static void video_driver_headset_screens(const settings_t *settings,
video_screen_xr_t *screens)
{
unsigned i;
for (i = 0; i < RETRO_VIDEO_VIEWS_MAX; i++)
{
video_screen_xr_t *screen = &screens[i];
screen->x = settings->floats.video_screen_xr_x[i];
screen->y = settings->floats.video_screen_xr_y[i];
screen->z = settings->floats.video_screen_xr_z[i];
screen->yaw = settings->floats.video_screen_xr_yaw[i];
screen->pitch = settings->floats.video_screen_xr_pitch[i];
screen->roll = settings->floats.video_screen_xr_roll[i];
screen->scale = settings->floats.video_screen_xr_scale[i];
screen->visible = settings->bools.video_screen_xr_visible[i];
video_views_xr_normalize(screen);
}
}
static void video_driver_headset_menu(const settings_t *settings,
video_screen_xr_t *menu)
{
menu->x = settings->floats.video_openxr_menu_xr_x;
menu->y = settings->floats.video_openxr_menu_xr_y;
menu->z = settings->floats.video_openxr_menu_xr_z;
menu->yaw = settings->floats.video_openxr_menu_xr_yaw;
menu->pitch = settings->floats.video_openxr_menu_xr_pitch;
menu->roll = settings->floats.video_openxr_menu_xr_roll;
menu->scale = settings->floats.video_openxr_menu_xr_scale;
menu->visible = true;
video_views_xr_normalize(menu);
}
void video_driver_build_info(video_frame_info_t *video_info)
{
video_viewport_settings_t *custom_vp = NULL;
@@ -6895,10 +6934,16 @@ void video_driver_build_info(video_frame_info_t *video_info)
video_info->bfi_dark_frames = settings->uints.video_bfi_dark_frames;
video_info->shader_subframes = video_st->headset_interval
? 1 : settings->uints.video_shader_subframes;
video_driver_headset_screens(settings, video_info->headset_screens);
video_driver_headset_menu(settings, &video_info->headset_menu);
video_info->headset_menu_placed = settings->bools.video_openxr_menu_placed;
video_info->headset_menu_follow = settings->bools.video_openxr_menu_follow;
video_info->headset_distance = settings->floats.video_openxr_distance;
video_info->headset_width = settings->floats.video_openxr_width;
video_info->headset_placement = settings->uints.video_openxr_placement;
video_info->headset_recenter = video_st->headset_recenter;
video_info->headset_edit_seq = video_st->headset_edit_seq;
video_info->headset_editing = video_st->headset_editing;
video_info->headset_interval = video_st->headset_interval;
video_info->headset_request_hz = video_st->headset_request_hz;
video_info->screen_layout = settings->uints.video_screen_layout;
+18
View File
@@ -786,6 +786,10 @@ typedef struct video_frame_info
* none, or no views are presented. Owned by video_driver_state_t,
* or under the threaded wrapper by the frame's slot. */
const video_windows_frame_t *windows;
/* Each screen's placement from its menu rows, normalized. */
video_screen_xr_t headset_screens[RETRO_VIDEO_VIEWS_MAX];
/* The menu's own place, used once headset_menu_placed. */
video_screen_xr_t headset_menu;
/* Headset output: the screens' distance and width in metres, where
* they are kept (enum video_openxr_placement), and a count of
* recenter requests the driver compares with the last it saw. */
@@ -793,6 +797,9 @@ typedef struct video_frame_info
float headset_width;
unsigned headset_placement;
unsigned headset_recenter;
/* Edit mode's count of let-go grabs: a quad set planned with it
* shows them where the hands left them. */
unsigned headset_edit_seq;
/* Each core frame shows for this many headset frames and waits for
* them in place of the window's vsync; 0 while the window paces. */
unsigned headset_interval;
@@ -800,8 +807,13 @@ typedef struct video_frame_info
float headset_request_hz;
unsigned screen_layout;
bool stereo_swap_eyes;
bool headset_menu_placed;
/* An unplaced menu opens in front of screen 1, not at its spot. */
bool headset_menu_follow;
/* The last frame sent again (paused, menu), not a new core frame. */
bool frame_repeat;
/* Edit mode moves the headset's items: the menu's quad stays up. */
bool headset_editing;
/* The tracked eyes' views, which the headset shows as its
* projection; -1 while it shows none. */
int headset_eye[2];
@@ -2070,6 +2082,8 @@ typedef struct
/* enum video_headset_view: the Headset View hotkey's choice, reset
* when content closes. */
unsigned headset_view;
/* Edit mode's let-go grabs, counted, main thread. */
unsigned headset_edit_seq;
/* The headset's runtime asked RetroArch to exit (OpenXR EXITING). */
retro_atomic_int_t headset_exit;
/* Headset pacing, main thread, never saved: the headset's measured
@@ -2088,6 +2102,7 @@ typedef struct
double headset_notice_fps;
unsigned headset_notice_hz;
bool headset_vsync;
bool headset_editing;
/* Set while video_driver_cached_frame() sends the last frame again. */
bool frame_repeat;
#ifdef HAVE_VIDEO_WINDOWS
@@ -2508,6 +2523,9 @@ void video_driver_headset_recenter(void);
* video_headset_view and return it; VIDEO_HEADSET_VIEW_LAST, unchanged,
* without a presenting headset. */
unsigned video_driver_headset_view_toggle(void);
/* Main thread: the headset's edit mode, on or off, and its count of
* let-go grabs, for the next frame snapshot. */
void video_driver_headset_edit(bool editing, unsigned seq);
/* Any thread: the headset's runtime asked RetroArch to exit. */
void video_driver_headset_exit_request(void);
+41
View File
@@ -15,11 +15,52 @@
#include <string.h>
#include <limits.h>
#include <float.h>
#include <math.h>
#include "video_views.h"
#include "video_defines.h"
void video_views_xr_reset(video_screen_xr_t *screens, unsigned count)
{
unsigned i;
if (!screens)
return;
if (count > RETRO_VIDEO_VIEWS_MAX)
count = RETRO_VIDEO_VIEWS_MAX;
for (i = 0; i < count; i++)
{
memset(&screens[i], 0, sizeof(screens[i]));
screens[i].scale = 1.0f;
screens[i].visible = true;
}
}
static float video_views_xr_value(float value, float low, float high,
float fallback)
{
if (!(value >= -FLT_MAX && value <= FLT_MAX))
return fallback;
if (value < low)
return low;
if (value > high)
return high;
return value;
}
void video_views_xr_normalize(video_screen_xr_t *screen)
{
if (!screen)
return;
screen->x = video_views_xr_value(screen->x, -10.0f, 10.0f, 0.0f);
screen->y = video_views_xr_value(screen->y, -10.0f, 10.0f, 0.0f);
screen->z = video_views_xr_value(screen->z, -10.0f, 10.0f, 0.0f);
screen->yaw = video_views_xr_value(screen->yaw, -180.0f, 180.0f, 0.0f);
screen->pitch = video_views_xr_value(screen->pitch, -180.0f, 180.0f, 0.0f);
screen->roll = video_views_xr_value(screen->roll, -180.0f, 180.0f, 0.0f);
screen->scale = video_views_xr_value(screen->scale, 0.1f, 10.0f, 1.0f);
}
bool video_views_validate(const struct retro_video_view *views,
unsigned num_views, video_views_map_t *out)
{
+61 -1
View File
@@ -45,7 +45,8 @@ enum video_screen_layout
VIDEO_SCREEN_LAYOUT_LAST
};
/* Headset input settings (video_openxr_controllers, video_openxr_laser). */
/* Headset input settings (video_openxr_controllers, video_openxr_laser,
* video_openxr_laser_beam). */
enum video_openxr_controllers
{
VIDEO_OPENXR_CONTROLLERS_COMBINED = 0,
@@ -73,6 +74,50 @@ enum video_headset_view
VIDEO_HEADSET_VIEW_LAST
};
enum video_openxr_laser_beam
{
VIDEO_OPENXR_LASER_BEAM_OFF = 0,
VIDEO_OPENXR_LASER_BEAM_EDIT,
VIDEO_OPENXR_LASER_BEAM_ALWAYS,
VIDEO_OPENXR_LASER_BEAM_LAST
};
/* Snap to Angles: the step edit mode turns and tilts a held item in
* (video_openxr_edit_snap_angles). */
enum video_openxr_edit_snap_angles
{
VIDEO_OPENXR_EDIT_SNAP_ANGLES_OFF = 0,
VIDEO_OPENXR_EDIT_SNAP_ANGLES_15,
VIDEO_OPENXR_EDIT_SNAP_ANGLES_30,
VIDEO_OPENXR_EDIT_SNAP_ANGLES_45,
VIDEO_OPENXR_EDIT_SNAP_ANGLES_90,
VIDEO_OPENXR_EDIT_SNAP_ANGLES_LAST
};
/* Hinge Gap: the gap edit mode leaves between screens whose edges
* meet (video_openxr_edit_hinge_gap). */
enum video_openxr_edit_hinge_gap
{
VIDEO_OPENXR_EDIT_HINGE_GAP_0 = 0,
VIDEO_OPENXR_EDIT_HINGE_GAP_1,
VIDEO_OPENXR_EDIT_HINGE_GAP_2,
VIDEO_OPENXR_EDIT_HINGE_GAP_3,
VIDEO_OPENXR_EDIT_HINGE_GAP_5,
VIDEO_OPENXR_EDIT_HINGE_GAP_10,
VIDEO_OPENXR_EDIT_HINGE_GAP_LAST
};
/* Edit Smoothing: how much edit mode steadies the controllers' aim
* (video_openxr_edit_smoothing). */
enum video_openxr_edit_smoothing
{
VIDEO_OPENXR_EDIT_SMOOTHING_OFF = 0,
VIDEO_OPENXR_EDIT_SMOOTHING_LOW,
VIDEO_OPENXR_EDIT_SMOOTHING_MEDIUM,
VIDEO_OPENXR_EDIT_SMOOTHING_HIGH,
VIDEO_OPENXR_EDIT_SMOOTHING_LAST
};
/* video_openxr_placement: where the headset keeps the screens. */
enum video_openxr_placement
{
@@ -108,6 +153,21 @@ typedef struct video_views_map
unsigned num_screens;
} video_views_map_t;
typedef struct video_screen_xr
{
float x;
float y;
float z;
float yaw;
float pitch;
float roll;
float scale;
bool visible;
} video_screen_xr_t;
void video_views_xr_reset(video_screen_xr_t *screens, unsigned count);
void video_views_xr_normalize(video_screen_xr_t *screen);
bool video_views_validate(const struct retro_video_view *views,
unsigned num_views, video_views_map_t *out);
+3237 -87
View File
File diff suppressed because it is too large. Load diff
+658 -9
View File
@@ -40,6 +40,8 @@ RETRO_BEGIN_DECLS
#define VIDEO_XR_GAP 0.02f
/* How far the UI floats in front of screen 0, in metres. */
#define VIDEO_XR_MENU_LIFT 0.10f
/* Following screen 0, the UI comes no nearer the anchor than this. */
#define VIDEO_XR_MENU_NEAREST 0.40f
/* The UI's quad fits in screen 0's width by this much of it. */
#define VIDEO_XR_MENU_MAX_H 0.75f
/* At most this many UI pixels per headset pixel across its quad. */
@@ -99,6 +101,11 @@ typedef struct video_xr_slot
unsigned dims; /* image size, VIDEO_SCALE_PACK; 0 unused */
unsigned layers; /* 2 for a stereo screen */
int view[2]; /* map view drawn per layer; -1 frame or UI */
video_xr_vec3_t base; /* its item's layout spot, the anchor's frame */
unsigned edit_seq; /* edit mode's count when it was planned */
/* A frame shown whole without the screens' settings (a views core's
* fallback): their edits would not move it, so it is no item. */
bool fixed;
} video_xr_slot_t;
typedef struct video_xr_quad_set
@@ -113,6 +120,11 @@ typedef struct video_xr_params
/* The frame's views; NULL or empty for one quad of the whole
* frame, shown at frame_aspect (0: frame_dims' own). */
const video_views_map_t *map;
/* Floating-screen adjustments by screen ID; NULL uses defaults. */
const video_screen_xr_t *screens;
/* The menu's own place from its spot; NULL: at its spot, or with
* menu_follow in front of screen 0. */
const video_screen_xr_t *menu;
float frame_aspect;
unsigned frame_dims;
/* Screen 0 is centred straight ahead of this position and yaw. */
@@ -126,11 +138,15 @@ typedef struct video_xr_params
unsigned rotation;
/* The UI layer's size, or 0 for no menu quad. */
unsigned ui_dims;
/* Edit mode's count of let-go grabs, carried by every slot. */
unsigned edit_seq;
/* Screens the headset's projection shows, a bit each: no quad, and
* the others keep their places. */
unsigned skip_screens;
bool swap_eyes;
/* An unplaced menu opens in front of screen 0 and moves with it. */
bool menu_follow;
/* The whole frame is the window, with the menu drawn in it. */
bool window;
/* The screens are drawn by the stock chain, which shrinks smoothly:
@@ -138,6 +154,451 @@ typedef struct video_xr_params
bool stock;
} video_xr_params_t;
/* Where video_xr_place starts each floating item, in the anchor's
* frame, before its adjustments: screen s of the map (a whole frame is
* screen 0) centred at pos[s], width[s] by height[s] metres at scale 1,
* from a source native[s] in size (VIDEO_SCALE_PACK); the menu's own
* spot at menu. count screens. */
typedef struct video_xr_layout
{
video_xr_vec3_t pos[RETRO_VIDEO_VIEWS_MAX];
video_xr_vec3_t menu;
float width[RETRO_VIDEO_VIEWS_MAX];
float height[RETRO_VIDEO_VIEWS_MAX];
unsigned native[RETRO_VIDEO_VIEWS_MAX];
unsigned count;
} video_xr_layout_t;
void video_xr_layout(const video_xr_params_t *p, video_xr_layout_t *out);
/* An item's pose in the anchor's frame: at base moved by screen's
* offsets, turned by its yaw, pitch and roll; NULL leaves it at base. */
void video_xr_item_pose(const video_xr_vec3_t *base,
const video_screen_xr_t *screen, video_xr_pose_t *out);
/* The offsets and turn that put an item from base at pose, both in the
* anchor's frame; out's scale and visible are left as they are. */
void video_xr_item_settings(const video_xr_vec3_t *base,
const video_xr_pose_t *pose, video_screen_xr_t *out);
/* Edit mode's stick: its deadzone as a fraction of its travel; at full
* travel past it, the push along the aim's ray in metres a second, the
* resize in doublings a second, and the free hand's turn and tilt in
* degrees a second, that stick within VIDEO_XR_EDIT_TURN_LOCK degrees
* of left-right only turning and of up-down only tilting; as it turns
* and tilts, Snap to Angles' steps each holding within
* VIDEO_XR_EDIT_TURN_HOLD degrees of it. A pulled item comes no nearer
* the hand than VIDEO_XR_EDIT_REACH metres. */
#define VIDEO_XR_EDIT_DEADZONE 0.2f
#define VIDEO_XR_EDIT_PUSH_RATE 1.5f
#define VIDEO_XR_EDIT_SCALE_RATE 1.0f
#define VIDEO_XR_EDIT_TURN_RATE 90.0f
#define VIDEO_XR_EDIT_TURN_LOCK 25.0f
#define VIDEO_XR_EDIT_TURN_HOLD 2.0f
#define VIDEO_XR_EDIT_REACH 0.1f
enum video_xr_grab_state
{
VIDEO_XR_GRAB_NONE = 0,
VIDEO_XR_GRAB_HELD,
/* Let go: the item stays where the hand left it until a quad set
* planned at release_seq or later shows it. */
VIDEO_XR_GRAB_RELEASED
};
/* A hand's hold on a floating item, in the anchor's frame. */
typedef struct video_xr_grab
{
video_xr_pose_t rel; /* the item in the aim pose's frame */
video_xr_pose_t last; /* where the hand last put it */
video_xr_quat_t turn; /* its turn from the aim's heading, kept
* while it does not tilt */
video_xr_vec3_t base; /* its layout spot */
float width; /* metres, at its scale now */
float height;
unsigned slot; /* its quads' slot */
unsigned release_seq;
unsigned state; /* enum video_xr_grab_state */
bool tilt; /* its pitch and roll follow the hand too */
unsigned char gap; /* Hinge Gap, whole centimetres */
/* The hinge it rested in as it was taken, whole from the grab while
* video_xr_grab_kept: its target's slot plus one (0, none), the
* target's edge and its own. */
unsigned char rest_slot;
unsigned char rest_on;
unsigned char rest_edge;
/* A push past the stop's hold let its hinge go
* (video_xr_grab_through): the stop holds VIDEO_XR_SNAP_STOP_ARM
* short until a hinge takes hold again. */
unsigned char through;
/* The hinge the main thread marks, where a let-go would settle it,
* kept a moment after the hand leaves: its target's slot plus one
* (0, none), the target's edge and its own. */
unsigned char mark_slot;
unsigned char mark_on;
unsigned char mark_edge;
} video_xr_grab_t;
/* Where g puts its item for an aim pose: the aim composed with g->rel,
* its turn g->turn from the aim's heading unless g->tilt, snapped to
* the screens of targets but those in skip, fading in from start, the
* item's pose as it was grabbed (video_xr_snap, video_xr_snap_fade;
* NULL targets: none, and start unread), the target of the hinge it was
* taken in pulling whole while video_xr_grab_kept, its offsets from
* g->base clamped to the settings' range. Writes the pose and, unless
* NULL, the offsets and turn and the snaps engaged or pulling
* (video_xr_snap). Returns whether a clamp held it back, never for a
* snap alone; the caller then holds g->rel again from pose, so the item
* resumes from the limit. */
bool video_xr_grab_pose(const video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_quad_set_t *targets, unsigned skip,
const video_xr_pose_t *start, video_xr_pose_t *pose,
video_screen_xr_t *out, unsigned *snapped);
/* g carries its item, turned q at aim, from here on: it keeps its yaw
* from the aim's heading, the way the ray points across the floor, and
* its pitch and roll. Near straight up or down, the heading blends into
* the one the hand's up gives, so a twist there turns it. */
void video_xr_grab_hold(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_quat_t *q);
/* The stick for dt seconds: past the deadzone y (up, +1) pushes g's
* item along the aim's ray and x (right, +1) enlarges it, each by how
* far past it is; *scale stays within the setting's range. */
void video_xr_grab_stick(video_xr_grab_t *g, float x, float y, double dt,
float *scale);
/* The free hand's stick for dt seconds, g's item held by aim: past the
* deadzone x (right, +1) turns it about the vertical through its
* middle, anticlockwise seen from above, and y (up, +1) tips its top
* back about its own left-right line through its middle, each by how
* far past it is; near one axis, only that one acts
* (VIDEO_XR_EDIT_TURN_LOCK). Returns the axes that acted, as
* VIDEO_XR_STEP_TURN and VIDEO_XR_STEP_TILT. */
#define VIDEO_XR_STEP_TURN 1u
#define VIDEO_XR_STEP_TILT 2u
unsigned video_xr_grab_turn(video_xr_grab_t *g, const video_xr_pose_t *aim,
float x, float y, double dt);
struct video_xr_hinge;
/* Holds g's item at a step, every size degrees (0: none), on the axes
* video_xr_grab_turn moved: the turn counts from facing head across the
* floor (NULL: no turn steps), the tilt from upright. rest is how far
* the stick has taken each past where it shows, so pushing on leaves
* the step; an axis not in axes keeps its rest. A snap to targets that
* would engage wins. With lid, a hinge that has let the item go (NULL:
* none), lid's axis holds about the item's edge lid->edge, as a lid
* turns, not its middle. Returns the axes held. */
unsigned video_xr_grab_step(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_vec3_t *head, float size, unsigned axes, float *rest,
const video_xr_quad_set_t *targets, unsigned skip,
const video_xr_pose_t *start, const struct video_xr_hinge *lid);
/* video_xr_grab_turn, but with lid (unless NULL) its axis turns about the
* item's edge lid->edge, as video_xr_grab_step holds it. */
unsigned video_xr_grab_turn_lid(video_xr_grab_t *g,
const video_xr_pose_t *aim, const struct video_xr_hinge *lid,
float x, float y, double dt);
/* The axes on which g's item, with rest as video_xr_grab_step keeps it,
* is outside every step's hold. NULL head: the turn is not known. */
unsigned video_xr_grab_off_step(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_vec3_t *head, float size,
const float *rest);
/* Snap to Screens: a held screen turns parallel or square to one within
* VIDEO_XR_SNAP_ANGLE degrees and lines up within VIDEO_XR_SNAP_DIST
* metres, or hinges on an edge. Each pull is whole in range and fades
* out over as far again; all fade past VIDEO_XR_SNAP_NEAR apart. */
#define VIDEO_XR_SNAP_DIST 0.03f
#define VIDEO_XR_SNAP_ANGLE 5.0f
#define VIDEO_XR_SNAP_NEAR 0.30f
/* Meeting edges click to the set gap within this of it (m). */
#define VIDEO_XR_SNAP_CLICK 0.01f
/* Else the gap is where the hand leaves it, touching to this (m). */
#define VIDEO_XR_SNAP_FREE 0.10f
/* A hinged edge pushed past touching stops there, whole this far past
* where the click lets go (m); past that the hinge ends. */
#define VIDEO_XR_SNAP_STOP 0.03f
/* The stop fades to none this far past where the click lets go (m). */
#define VIDEO_XR_SNAP_STOP_FADE 0.055f
/* A push that ended the hinge holds this short of where it takes hold
* again; the stop ticks again once the edge is this far apart (m). */
#define VIDEO_XR_SNAP_STOP_ARM 0.005f
/* The snaps engaged, each whole: video_xr_snap's low bits. */
#define VIDEO_XR_SNAP_TURN 1u
#define VIDEO_XR_SNAP_X 2u
#define VIDEO_XR_SNAP_Y 4u
#define VIDEO_XR_SNAP_Z 8u
#define VIDEO_XR_SNAP_ALL 15u
/* Each snap pulling at all, whole or fading (or a hinge's stop near
* touching): its bit shifted up by this, also in what video_xr_snap
* returns. */
#define VIDEO_XR_SNAP_PULL_SHIFT 4
#define VIDEO_XR_SNAP_PULLING(bits) ((bits) << VIDEO_XR_SNAP_PULL_SHIFT)
/* Snaps a screen in slot, width x height metres, held at pose, to the
* other screens of set no hand holds (skip: slots, as bits), every
* pull scaled by fade (0 none, 1 whole; a snap is engaged only whole),
* edges that meet touching. Never the menu, held or as a target; a
* stereo pair is one; a fixed slot none. Returns the snaps engaged and
* VIDEO_XR_SNAP_PULLING of those pulling at all. Keeps nothing between
* calls. */
unsigned video_xr_snap(const video_xr_quad_set_t *set, unsigned slot,
unsigned skip, float width, float height, float fade,
video_xr_pose_t *pose);
/* A hinge engaged: an edge of the held screen exactly in a target's
* plane, parallel to its edge at a gap the hinge holds, both screens
* either side of that line at any angle; also in what video_xr_snap
* returns. */
#define VIDEO_XR_SNAP_HINGE 256u
/* That hinge's edge exactly the set gap from the target's, clicked to
* it, or held touching by its stop; with its pulling bit, also in what
* video_xr_snap returns. */
#define VIDEO_XR_SNAP_GAP 512u
/* The hinge video_xr_snap_hinge engaged, in the anchor's frame: the held
* edge's middle on the hinge's line, the line's way, the way from it
* that is flat with the target, the held screen's edge (0 top,
* 1 bottom, 2 right, 3 left), the screens' angle from flat in degrees
* about dir, the gap in metres from the target's edge to the line, in
* the target's plane, the free hand's stick axis whose turn lies
* nearer the line (VIDEO_XR_STEP_TILT or VIDEO_XR_STEP_TURN), and the
* target's slot and the edge of it the hinge is on. */
typedef struct video_xr_hinge
{
video_xr_vec3_t point;
video_xr_vec3_t dir;
video_xr_vec3_t flat;
float angle;
float gap;
unsigned edge;
unsigned axis;
unsigned slot;
unsigned on;
} video_xr_hinge_t;
/* video_xr_snap, edges that meet gap metres apart, and the hinge it
* engaged unless hinge is NULL; hinge is written only with
* VIDEO_XR_SNAP_HINGE returned. */
unsigned video_xr_snap_hinge(const video_xr_quad_set_t *set, unsigned slot,
unsigned skip, float width, float height, float gap, float fade,
video_xr_pose_t *pose, video_xr_hinge_t *hinge);
/* A held screen turned q, its edge h->edge along h's line: its angle
* from flat about h->dir, in degrees. */
float video_xr_hinge_angle(const video_xr_hinge_t *h,
const video_xr_quat_t *q);
/* Where hinge h shows its line, its held screen width x height metres,
* on target: from a to b along h->dir where the held edge and target's
* edge h->on overlap, midway across the gap; its layer is drawn as the
* beam is, VIDEO_XR_HINGE_WIDTH wide, each end VIDEO_XR_HINGE_LIFT
* nearer the eye. False where they do not overlap. */
#define VIDEO_XR_HINGE_WIDTH 0.006f
#define VIDEO_XR_HINGE_LIFT 0.01f
bool video_xr_hinge_line(const video_xr_hinge_t *h, float width,
float height, const video_xr_quad_t *target, video_xr_vec3_t *a,
video_xr_vec3_t *b);
/* h, its slots and edges kept, placed on those edges as they are now:
* its held one, of a screen width x height metres at pose, and
* target's. Its line runs midway between their ways through the held
* edge's middle (point), flat and gap from the target's edge to it, so
* it lies midway across and in depth; crossed, across on the edge drawn
* over the other (the held one's if over). */
void video_xr_hinge_place(video_xr_hinge_t *h, const video_xr_pose_t *pose,
float width, float height, const video_xr_quad_t *target, bool over);
/* Two screens at rest where a hinge would hold one of them carried:
* slot's edge edge (0 top, 1 bottom, 2 right, 3 left) on target's edge
* on. */
typedef struct video_xr_rest
{
unsigned char slot;
unsigned char edge;
unsigned char target;
unsigned char on;
} video_xr_rest_t;
/* The screens of set but those in skip (slots, as bits) resting
* exactly in a hinge, as video_xr_snap_hinge's targets go: each pair
* whose poses pass the test that engages a carried screen's hinge, gap
* metres apart, with either carried (the earlier in set first if
* both), where that hinge held whole, nothing else pulling, leaves the
* carried one unmoved; up to max into rest. Returns how many. */
unsigned video_xr_rest_hinges(const video_xr_quad_set_t *set,
unsigned skip, float gap, video_xr_rest_t *rest, unsigned max);
/* The hinge a screen in slot, width x height metres resting at pose, is
* taken in: of the hinges it rests in carried, on screens of set but
* those in skip, gap metres apart, the one video_xr_snap_hinge ranks
* best, if holding it whole there, nothing else pulling yet, moves
* nothing. False with none. */
bool video_xr_rest_hinge(const video_xr_quad_set_t *set, unsigned slot,
unsigned skip, float width, float height, float gap,
const video_xr_pose_t *pose, video_xr_hinge_t *hinge);
/* Where a hand taking a screen width x height in slot, resting at pose
* in h's hinge (video_xr_rest_hinge), gap metres the set gap, holds it,
* into pose: touching, pushed VIDEO_XR_SNAP_STOP_ARM past where the stop
* starts, which holds it there, so a let-go after a drift short of that
* stays touching; else where it rests. */
void video_xr_rest_take(const video_xr_quad_set_t *set, unsigned slot,
float width, float height, float gap, const video_xr_hinge_t *h,
video_xr_pose_t *pose);
/* A screen let go hinged settles: its edge to the set gap from within
* VIDEO_XR_HINGE_SETTLE metres of it, apart; touching from the stop's
* hold; level along the edge from within the level pull's reach; else
* where it is. */
#define VIDEO_XR_HINGE_SETTLE 0.03f
/* Where screen slot, width x height metres, its hand putting it at
* hand, settles in hinge h (its target's slot and both edges) on set's
* screens but those in skip, gap metres the set gap: h held whole from
* there, nothing else pulling, then to its settle points, into pose.
* False if h does not engage it there or what it gives is not exact
* (video_xr_rest_hinges would not list it). */
bool video_xr_hinge_settle(const video_xr_quad_set_t *set, unsigned slot,
unsigned skip, float width, float height, float gap,
const video_xr_hinge_t *h, const video_xr_pose_t *hand,
video_xr_pose_t *pose);
/* video_xr_hinge_settle for g's item where aim puts it with no snap. */
bool video_xr_grab_settle(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
unsigned skip, const video_xr_hinge_t *h, video_xr_pose_t *pose);
/* video_xr_grab_pose with targets and no settings, also giving the
* hinge it holds (VIDEO_XR_SNAP_HINGE in the snaps returned, never
* with a clamp holding it back, nor unless the item's own edge is
* within VIDEO_XR_SNAP_DIST metres of the target's plane and
* VIDEO_XR_SNAP_ANGLE degrees of the hinge's line, at a gap the hinge
* holds whole). */
unsigned video_xr_grab_hinge(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
unsigned skip, const video_xr_pose_t *start, video_xr_pose_t *pose,
video_xr_hinge_t *hinge);
/* Whether g's item, where it is with no snap, has its edge h->edge where
* h's hinge could take it again, against h's target where targets has
* it now: within reach metres of its plane, at a gap the hinge
* reaches, fades included, or past touching where the stop holds it
* whole (video_xr_grab_through), and overlapping its edge along the
* line. The stick swings about it within VIDEO_XR_SNAP_DIST; the hinge
* a hold was taken in keeps it within VIDEO_XR_HINGE_REACH, past where
* it takes hold (video_xr_grab_kept). */
#define VIDEO_XR_HINGE_REACH (1.5f * VIDEO_XR_SNAP_DIST)
bool video_xr_grab_near_hinge(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
const video_xr_hinge_t *h, float reach);
/* Whether g's item, where it is with no snap, has its edge h->edge
* pushed past touching h's target farther than the stop holds it
* whole, VIDEO_XR_SNAP_STOP_ARM less with g->through: a push that lets
* the hinge go. */
bool video_xr_grab_through(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
const video_xr_hinge_t *h);
/* Whether g still carries the hinge it was taken in (g->rest_slot): its
* edge within VIDEO_XR_HINGE_REACH of where that hinge could take it
* again (video_xr_grab_near_hinge), or past that while its fade from
* start is short of whole (video_xr_snap_fade), so leaving the reach
* does not hop it. Writes that hinge's slots and edges to kept. */
bool video_xr_grab_kept(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
const video_xr_pose_t *start, video_xr_hinge_t *kept);
/* The hinge the main thread marks for g's item (g->mark_slot) into h,
* placed on set's edges as they are with the item at pose. False with
* no mark, or its target in holding (a bit per slot a hand holds) or
* not in set's quads. */
bool video_xr_grab_marked(const video_xr_grab_t *g,
const video_xr_pose_t *pose, const video_xr_quad_set_t *set,
unsigned holding, video_xr_hinge_t *h);
/* g holds its item at pose for aim, its place and its turn. */
void video_xr_grab_put(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_pose_t *pose);
/* g holds its item in h's plane as video_xr_grab_put_line, at its own
* angles, or turned (an engaged turn to flat or square) at those of
* pose, the shown pose h came with, about its edge h->edge's middle:
* only what the snap holds whole, so it still shows as pose. */
void video_xr_grab_put_hinge(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_hinge_t *h, const video_xr_pose_t *pose, bool turned);
/* g holds its item where it is with no snap, moved straight out of the
* target's plane until the middle of its edge h->edge is in it: its
* turn and its gap kept, but a gap the click holds goes to g's set
* one. */
void video_xr_grab_put_line(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_hinge_t *h);
/* g's item, where it is with no snap, turned deg degrees about its own
* edge h->edge (about h->dir, its angle from flat growing by deg). */
void video_xr_grab_swing(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_hinge_t *h, double deg);
/* video_xr_grab_turn, but with h (unless NULL) its axis h->axis swings
* the item about its own edge h->edge instead, as far, keeping its gap. */
unsigned video_xr_grab_turn_hinge(video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_hinge_t *h, float x,
float y, double dt);
/* video_xr_grab_step for an item hinged by h: h->axis holds at a step
* of the angle from flat, every size degrees; the other axis holds at
* none, the hinge's snap winning; a turn to flat or square that would
* engage wins over both. */
unsigned video_xr_grab_step_hinge(video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_hinge_t *h, float size,
unsigned axes, float *rest, const video_xr_quad_set_t *targets,
unsigned skip, const video_xr_pose_t *start);
/* h->axis if g's item, rest as video_xr_grab_step_hinge keeps it, is
* outside every step's hold of its angle from flat; else 0. */
unsigned video_xr_grab_off_hinge(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_hinge_t *h, float size,
const float *rest);
/* Snapping fades in from a grab: 0 at start, the item's pose as it was
* grabbed, rising evenly to 1 once pose is VIDEO_XR_SNAP_FADE_DIST
* metres or VIDEO_XR_SNAP_FADE_ANGLE degrees from it, whichever comes
* first, so a screen taken near a line does not hop onto it. */
#define VIDEO_XR_SNAP_FADE_DIST 0.05f
#define VIDEO_XR_SNAP_FADE_ANGLE 10.0f
float video_xr_snap_fade(const video_xr_pose_t *start,
const video_xr_pose_t *pose);
/* Edit Smoothing's filter for a hand's aim: a One Euro filter (Casiez
* et al. 2012) on its position and its turn, smoothing a slow hand
* hard and a fast one little. */
typedef struct video_xr_smooth
{
video_xr_pose_t raw; /* the last step's raw pose */
video_xr_pose_t pose; /* and what it gave */
float speed; /* metres a second, filtered */
float turn; /* radians a second, filtered */
bool on; /* false: the next step starts afresh */
} video_xr_smooth_t;
/* A step jumping further than these starts afresh. */
#define VIDEO_XR_SMOOTH_JUMP_M 0.5f
#define VIDEO_XR_SMOOTH_JUMP_DEG 45.0f
/* The speeds' own cutoff in Hz. */
#define VIDEO_XR_SMOOTH_DCUTOFF 1.0f
/* s one step on, dt seconds after the last, to raw, as level (enum
* video_openxr_edit_smoothing) smooths: the pose into out. Off, a first
* step (s->on false, or dt not above 0) or a jump: out is raw, and the
* filter starts from it. */
void video_xr_smooth_step(video_xr_smooth_t *s, unsigned level,
const video_xr_pose_t *raw, double dt, video_xr_pose_t *out);
void video_xr_pose_identity(video_xr_pose_t *pose);
/* out = q applied to v; out may be v. */
@@ -153,6 +614,17 @@ bool video_xr_anchor_from_head(const video_xr_pose_t *head,
void video_xr_compose(const video_xr_pose_t *a, const video_xr_pose_t *b,
video_xr_pose_t *out);
/* out = a's inverse: video_xr_compose(a, out) is the identity; out may
* be a. */
void video_xr_pose_invert(const video_xr_pose_t *a, video_xr_pose_t *out);
/* q's turn in degrees, in the order an item's adjustments turn it: yaw
* about +Y, pitch about +X, roll about +Z. Pitch is within [-90, 90];
* straight up or down, roll is 0 and yaw takes the whole turn about the
* vertical. */
void video_xr_euler(const video_xr_quat_t *q, float *yaw, float *pitch,
float *roll);
/* Lazy Follow: the screens stay until the head's heading is this far
* from theirs, or the head this far from where they were placed from;
* then they glide back, closing about two thirds of the way each
@@ -180,6 +652,13 @@ unsigned video_xr_image_dims(float width_m, float height_m, float distance,
bool video_xr_shrinks(unsigned image_dims, unsigned source_dims,
unsigned rotation);
/* A slot's image size: while editing, current (the image it has, 0 for
* none) if planned is within 0.8 to 1.25 times it on both axes, so a
* screen being moved is not given a new swapchain at each distance;
* planned otherwise. */
unsigned video_xr_keep_dims(unsigned planned, unsigned current,
bool editing);
void video_xr_place(const video_xr_params_t *p, video_xr_quad_set_t *out);
/* Where a ray (origin + t * dir, t >= 0) meets a quad's front face:
@@ -187,6 +666,15 @@ void video_xr_place(const video_xr_params_t *p, video_xr_quad_set_t *out);
bool video_xr_ray_hit(const video_xr_quad_t *q, const video_xr_vec3_t *origin,
const video_xr_vec3_t *dir, float *u, float *v, float *t);
/* Whether eye is behind q's plane, where runtimes draw nothing of a
* quad layer. */
bool video_xr_quad_behind(const video_xr_quad_t *q,
const video_xr_vec3_t *eye);
/* pose turned half round its own up: in the same plane, facing the
* other way. */
void video_xr_turn_back(video_xr_pose_t *pose);
/* A point on a quad as packed-frame pointer coordinates: any copy of a
* screen lands in its EYE_NONE or left view at the same fraction, a
* whole frame spreads over the frame. map and frame_dims are the
@@ -219,38 +707,199 @@ void video_xr_place_window(const video_xr_params_t *p, bool menu,
video_xr_quad_set_t *out,
video_views_rect_t rects[VIDEO_XR_MAX_SLOTS][2]);
/* The laser's dot image is this many pixels square. */
#define VIDEO_XR_CURSOR_DIM 32
/* The laser's image: its parts side by side, a clear column between
* each and each centred on the middle row (GL's rows run bottom-up):
* dot, outlines, handle bars, beam strips, back, hinge strip, tags, cut
* outlines. */
#define VIDEO_XR_CURSOR_DIM 32
#define VIDEO_XR_OUTLINE_DIM 256
#define VIDEO_XR_OUTLINE_BAND 2
#define VIDEO_XR_OUTLINE_EDGE (3 * VIDEO_XR_OUTLINE_BAND)
#define VIDEO_XR_OUTLINE_FAINT 64
#define VIDEO_XR_HANDLE_COLS 128
#define VIDEO_XR_HANDLE_ROWS 8
#define VIDEO_XR_BEAM_LEN 128
#define VIDEO_XR_BEAM_ROWS 4
#define VIDEO_XR_HINGE_COLS 128
#define VIDEO_XR_HINGE_ROWS 4
#define VIDEO_XR_TAGS (VIDEO_XR_MAX_SLOTS + 1)
#define VIDEO_XR_TAG_COLS 128
#define VIDEO_XR_TAG_ROWS \
(VIDEO_XR_TAG_TEXT_ROWS + 2 * VIDEO_XR_TAG_INSET)
#define VIDEO_XR_OUTLINE_X(lit) \
(VIDEO_XR_CURSOR_DIM + 1 + (lit) * (VIDEO_XR_OUTLINE_DIM + 1))
#define VIDEO_XR_HANDLE_X(lit) \
(VIDEO_XR_OUTLINE_X(2) + (lit) * (VIDEO_XR_HANDLE_COLS + 1))
#define VIDEO_XR_BEAM_X(miss) \
(VIDEO_XR_HANDLE_X(2) + (miss) * (VIDEO_XR_BEAM_LEN + 1))
#define VIDEO_XR_BACK_DIM 8
#define VIDEO_XR_BACK_MARGIN 4
#define VIDEO_XR_BACK_CELL (VIDEO_XR_BACK_DIM + 2 * VIDEO_XR_BACK_MARGIN)
/* Before the tags, so a runtime too narrow for the tags keeps these. */
#define VIDEO_XR_BACK_X VIDEO_XR_BEAM_X(2)
#define VIDEO_XR_HINGE_X (VIDEO_XR_BACK_X + VIDEO_XR_BACK_CELL + 1)
#define VIDEO_XR_TAG_X(tag) \
(VIDEO_XR_HINGE_X + VIDEO_XR_HINGE_COLS + 1 \
+ (tag) * (VIDEO_XR_TAG_COLS + 1))
/* An outline for each cut a hinge makes (top, bottom, right, left, top
* and bottom, right and left), faint ones then bright, each an outline's
* square without the rims of its cut edges, VIDEO_XR_OUTLINE_CROP
* texels deep, so a cut changes only the outline's image. */
#define VIDEO_XR_OUTLINE_CUTS 6
#define VIDEO_XR_CUT_X(cut, lit) \
(VIDEO_XR_TAG_X(VIDEO_XR_TAGS) \
+ ((lit) * VIDEO_XR_OUTLINE_CUTS + (cut)) * (VIDEO_XR_OUTLINE_DIM + 1))
#define VIDEO_XR_CURSOR_W (VIDEO_XR_CUT_X(0, 2) - 1)
/* The image without the cut outlines, then without the tags too, for
* runtimes whose swapchains are narrower. */
#define VIDEO_XR_CURSOR_TAGGED_W (VIDEO_XR_TAG_X(VIDEO_XR_TAGS) - 1)
#define VIDEO_XR_CURSOR_UNTAGGED_W (VIDEO_XR_TAG_X(0) - 1)
#define VIDEO_XR_CURSOR_H VIDEO_XR_OUTLINE_DIM
#define VIDEO_XR_CURSOR_Y ((VIDEO_XR_CURSOR_H - VIDEO_XR_CURSOR_DIM) / 2)
#define VIDEO_XR_HANDLE_Y ((VIDEO_XR_CURSOR_H - VIDEO_XR_HANDLE_ROWS) / 2)
#define VIDEO_XR_BEAM_Y ((VIDEO_XR_CURSOR_H - VIDEO_XR_BEAM_ROWS) / 2)
#define VIDEO_XR_TAG_Y ((VIDEO_XR_CURSOR_H - VIDEO_XR_TAG_ROWS) / 2)
#define VIDEO_XR_BACK_Y ((VIDEO_XR_CURSOR_H - VIDEO_XR_BACK_CELL) / 2)
#define VIDEO_XR_HINGE_Y ((VIDEO_XR_CURSOR_H - VIDEO_XR_HINGE_ROWS) / 2)
/* Rows of the hinge's column nothing is drawn in, either way up. */
#define VIDEO_XR_HINGE_HIDDEN_Y 0
/* The beam's strips: white at this level, fading in over their first
* FADE_IN texels; the one into nothing out over its last FADE_OUT. */
#define VIDEO_XR_BEAM_LEVEL 160
#define VIDEO_XR_BEAM_FADE_IN 8
#define VIDEO_XR_BEAM_FADE_OUT 32
/* A back's grey, opaque. */
#define VIDEO_XR_BACK_LEVEL 40
/* A hinge's line: green, red and blue equal, at this alpha. */
#define VIDEO_XR_HINGE_RB 60
#define VIDEO_XR_HINGE_G 220
#define VIDEO_XR_HINGE_LEVEL 166
/* Its pixels, RGBA8 premultiplied: a white dot with a dark rim and a
* one-pixel soft edge, grey so RGBA and BGRA hold the same bytes. px
* holds VIDEO_XR_CURSOR_DIM squared times four bytes. */
/* Its pixels, RGBA8 premultiplied, grey or with red equal to blue, so
* RGBA and BGRA hold the same bytes: a white dot with a dark rim and a
* one-pixel soft edge; outline rims and handle bars in black and white,
* the faint ones at VIDEO_XR_OUTLINE_FAINT alpha; white beam strips;
* the back's grey cell; the hinge's green strip. px holds
* VIDEO_XR_CURSOR_W by VIDEO_XR_CURSOR_H times four bytes. */
void video_xr_cursor_pixels(uint8_t *px);
/* A tag's words: light over a dark halo this many texels wide, kept
* this far inside its cell, at this level and alpha; drawn at this
* font size, in rows for a line half again as tall (Noto Sans's is
* 1.44 times its size). */
#define VIDEO_XR_TAG_HALO 2
#define VIDEO_XR_TAG_INSET (VIDEO_XR_TAG_HALO + 1)
#define VIDEO_XR_TAG_FAINT 160
#define VIDEO_XR_TAG_FONT 16
#define VIDEO_XR_TAG_TEXT_ROWS (VIDEO_XR_TAG_FONT * 3 / 2)
/* Tag tag's cell of the laser's image from cov, the cell's text as
* coverage (VIDEO_XR_TAG_COLS by VIDEO_XR_TAG_ROWS, 0 to 255): light
* text over its dark halo; bottom_up writes the rows last first, for
* an image whose rows run bottom up, as GL's. */
void video_xr_tag_pixels(uint8_t *px, unsigned tag, const uint8_t *cov,
bool bottom_up);
/* The cut outlines' cells of the laser's image; bottom_up swaps the
* top's and the bottom's, for an image whose rows run bottom up. */
void video_xr_cut_pixels(uint8_t *px, bool bottom_up);
/* The laser's dot: this wide per metre along the ray, never narrower
* than VIDEO_XR_CURSOR_MIN, just in front of the quad it is on. */
#define VIDEO_XR_CURSOR_SCALE 0.012f
#define VIDEO_XR_CURSOR_MIN 0.005f
#define VIDEO_XR_CURSOR_LIFT 0.002f
/* Whether a laser (enum video_openxr_laser) can point at q. Auto: the
* menu or a window while the menu is open, and screens 1 and up.
* Always: every screen and a whole frame too. Off: nothing. */
/* Whether a laser (enum video_openxr_laser, or VIDEO_XR_LIVE_EDIT) can
* point at q. Auto: the menu or a window while the menu is open, and
* screens 1 and up. Always: every screen and a whole frame too. Off:
* nothing. Edit mode's: every item. */
#define VIDEO_XR_LIVE_EDIT VIDEO_OPENXR_LASER_LAST
bool video_xr_quad_live(const video_xr_quad_t *q, unsigned laser,
bool menu_open);
/* Edit mode takes the menu by a handle bar centred under its bottom
* edge, in its plane: this fraction of its width, this many metres
* tall and this many below its outline. A ray within
* VIDEO_XR_HANDLE_MARGIN metres of the bar on any side takes it where
* it meets nothing else. */
#define VIDEO_XR_HANDLE_WIDTH 0.4f
#define VIDEO_XR_HANDLE_HEIGHT 0.03f
#define VIDEO_XR_HANDLE_GAP 0.005f
#define VIDEO_XR_HANDLE_MARGIN 0.03f
/* The menu quad's handle: the menu's quad, moved and sized to it. */
void video_xr_menu_handle(const video_xr_quad_t *menu, video_xr_quad_t *out);
/* The nearest live quad the ray meets, as an index into set->quads, or
* -1; of a stereo screen's two quads, the first. t is in units of
* dir's length. */
* dir's length. Edit mode's laser passes fixed slots' quads and meets
* the menu only around its handle, at (u, v) in the menu's own frame:
* on the bar before anything, even a nearer screen; in its margin,
* which on a small menu reaches onto the menu's bottom edge, only where
* it meets nothing else. Through the menu, what is behind. Edit mode
* meets either face, so an item turned away stays in reach. */
int video_xr_pick(const video_xr_quad_set_t *set, unsigned laser,
bool menu_open, const video_xr_vec3_t *origin,
const video_xr_vec3_t *dir, float *u, float *v, float *t);
/* video_xr_pick with edit mode's laser, for a hand whose laser has the
* menu's handle stuck (stuck): its margin then wins over anything the
* ray meets, as the bar does. *stick, unless NULL: whether it stays
* stuck, the ray on the bar, or stuck and still in its margin. */
int video_xr_pick_edit(const video_xr_quad_set_t *set, bool stuck,
const video_xr_vec3_t *origin, const video_xr_vec3_t *dir,
float *u, float *v, float *t, bool *stick);
/* The dot where a ray dist metres long meets q at (u, v): its pose,
* facing as q does. Returns its width in metres. */
float video_xr_cursor(const video_xr_quad_t *q, float u, float v,
float dist, video_xr_pose_t *pose);
/* The laser's beam: this wide, and this long with nothing under the
* laser. */
#define VIDEO_XR_BEAM_WIDTH 0.004f
#define VIDEO_XR_BEAM_REACH 1.5f
/* A beam len metres long from o along the unit ray d: its pose at its
* middle, its x down the ray, turned about the ray so its face is
* towards eye. */
void video_xr_beam(const video_xr_vec3_t *o, const video_xr_vec3_t *d,
float len, const video_xr_vec3_t *eye, video_xr_pose_t *pose);
/* Edit mode's tag: this tall, as wide as its cell's shape, this far
* above the hand's aim. */
#define VIDEO_XR_TAG_HEIGHT 0.02f
#define VIDEO_XR_TAG_LIFT 0.05f
/* The tag over a hand at hand: its pose, its face towards eye and its
* x level; with eye straight above or below, any level x. */
void video_xr_tag(const video_xr_vec3_t *hand, const video_xr_vec3_t *eye,
video_xr_pose_t *pose);
/* Edit mode's outline round q: q, grown so the rim's inner edge, with
* the half texel filtering spreads it by, is VIDEO_XR_OUTLINE_GAP
* metres outside q's on every side. */
#define VIDEO_XR_OUTLINE_GAP 0.005f
void video_xr_outline(const video_xr_quad_t *q, video_xr_quad_t *out);
/* Outline o, as video_xr_outline made it and turned to the head when
* back, without the rims of its item's edges in hide (bit e for edge
* e: 0 top, 1 bottom, 2 right, 3 left; one, or two opposite): turned a
* quarter about its face for a top or bottom, then cut to the *w texels
* of its square's x from *x. VIDEO_XR_OUTLINE_CROP texels go at a
* side, so filtering's half texel there meets its clear middle; never
* its y, which GL's bottom-up rows would flip. */
#define VIDEO_XR_OUTLINE_CROP 8
void video_xr_outline_crop(video_xr_quad_t *o, unsigned hide, bool back,
unsigned *x, unsigned *w);
/* Where in the laser's image at full width the outline without the
* rims in hide shows, as video_xr_outline_crop takes hide and back:
* VIDEO_XR_OUTLINE_X(lit) for none, else its VIDEO_XR_CUT_X cell, a top
* or bottom over a side. The outline keeps its pose and size. */
unsigned video_xr_outline_cell(unsigned hide, bool back, unsigned lit);
/* Headset pacing. The XR thread's filter over each frame's predicted
* display period: a period is published once VIDEO_XR_PERIODS frames
* agree within 1% of their median, and again when a new median is more
File diff suppressed because it is too large. Load diff
+24 -5
View File
@@ -45,10 +45,10 @@ struct rarch_joypad_driver;
void input_openxr_register(void);
/* Syncs the controllers, and connects or disconnects their devices;
* once per input poll, with Max Users, the Headset Controllers and
* Laser Pointer settings and the axis threshold. */
* once per input poll, with Max Users, the Headset Controllers, Laser
* Pointer and Laser Beam settings and the axis threshold. */
void input_openxr_poll(unsigned max_users, unsigned controllers,
unsigned laser, float threshold);
unsigned laser, unsigned beam, float threshold);
/* @joypad, or while the devices have slots a joypad driver that
* answers theirs and passes every other to @joypad. */
@@ -69,10 +69,29 @@ void input_openxr_reannounce(void);
* headset's. */
bool input_openxr_autoconfigure(unsigned slot);
/* RARCH_MENU_TOGGLE, RARCH_HEADSET_RECENTER, RARCH_LASER_POINTER_TOGGLE
* or RARCH_HEADSET_VIEW_TOGGLE, from the headset's own actions. */
/* RARCH_MENU_TOGGLE, RARCH_HEADSET_RECENTER, RARCH_LASER_POINTER_TOGGLE,
* RARCH_HEADSET_VIEW_TOGGLE or RARCH_HEADSET_EDIT_SCREENS, from the
* headset's own actions. */
bool input_openxr_hotkey(unsigned id);
/* Edit Screens, main thread, between frames: a pause draws a frame.
* Toggle: stops edit mode, false; otherwise whether it can start, the
* desktop told when not. Begin, once the menu was asked to close: on at
* once, pausing running content when @pause (While Editing Screens)
* asks, held screens snapping when @snap (Snap to Screens) does;
* Snap to Angles' step, @snap_angles, Hinge Gap, @hinge_gap, and Edit
* Smoothing's level, @smoothing, hold until edit mode ends; a menu that
* stayed open (no content) is told instead. End: off at once, as a
* video reinit wants. */
bool input_openxr_edit_toggle(void);
void input_openxr_edit_begin(bool pause, bool snap, unsigned snap_angles,
unsigned hinge_gap, unsigned smoothing);
void input_openxr_edit_end(void);
/* Main thread: what each hand holds is let go where it is, as before a
* recenter. Edit mode stays on. */
void input_openxr_edit_release(void);
/* The laser as RETRO_DEVICE_POINTER or RETRO_DEVICE_LIGHTGUN on port 0,
* while it can point (the headset focused, a screen live for the
* laser): true, with *res set. A miss reads as offscreen. */
+1
View File
@@ -191,6 +191,7 @@ enum
RARCH_HEADSET_RECENTER,
RARCH_LASER_POINTER_TOGGLE,
RARCH_HEADSET_VIEW_TOGGLE,
RARCH_HEADSET_EDIT_SCREENS,
RARCH_FPS_TOGGLE,
RARCH_STATISTICS_TOGGLE,
RARCH_AI_SERVICE,
+18 -1
View File
@@ -12427,6 +12427,9 @@ static void input_hotkeys_maybe_held(input_driver_state_t *input_st,
if (input_openxr_hotkey(RARCH_HEADSET_VIEW_TOGGLE))
maybe[RARCH_HEADSET_VIEW_TOGGLE >> 5] |=
(1u << (RARCH_HEADSET_VIEW_TOGGLE & 31));
if (input_openxr_hotkey(RARCH_HEADSET_EDIT_SCREENS))
maybe[RARCH_HEADSET_EDIT_SCREENS >> 5] |=
(1u << (RARCH_HEADSET_EDIT_SCREENS & 31));
#endif
}
@@ -15342,6 +15345,19 @@ static void input_driver_platform_do(enum input_platform_request req)
if (input_driver_mouse_grabbed())
command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL);
break;
case INPUT_PLATFORM_PAUSE:
#ifdef HAVE_CHEEVOS
/* As the Pause hotkey: hardcore allows a pause only so often. */
if ( rcheevos_hardcore_active()
&& !(runloop_get_flags() & RUNLOOP_FLAG_PAUSED)
&& !rcheevos_is_pause_allowed())
break;
#endif
command_event(CMD_EVENT_PAUSE, NULL);
break;
case INPUT_PLATFORM_UNPAUSE:
command_event(CMD_EVENT_UNPAUSE, NULL);
break;
default:
break;
}
@@ -15963,7 +15979,8 @@ void input_driver_poll(void)
input->poll(input_st->current_data);
#ifdef HAVE_OPENXR
input_openxr_poll(max_users, settings->uints.video_openxr_controllers,
settings->uints.video_openxr_laser, input_axis_threshold);
settings->uints.video_openxr_laser,
settings->uints.video_openxr_laser_beam, input_axis_threshold);
#endif
input_poll_generation++;
input_port_keys_at_poll(input_st->current_driver,
+5 -2
View File
@@ -1353,8 +1353,9 @@ void input_driver_set_nonblocking(bool on);
/* What a platform asks of the frontend through its input driver: the
* home or power button, a panic combination, the system closing the
* app, its window going away and coming back. The driver says what
* happened; the frontend decides what is done. */
* app, its window going away and coming back, the content paused and
* resumed. The driver says what happened; the frontend decides what is
* done. */
enum input_platform_request
{
INPUT_PLATFORM_QUIT = 0, /* quit as the Quit hotkey does */
@@ -1362,6 +1363,8 @@ enum input_platform_request
INPUT_PLATFORM_MENU_TOGGLE, /* the platform's menu gesture */
INPUT_PLATFORM_REINIT_VIDEO, /* the window's surface was lost */
INPUT_PLATFORM_FOCUS_LOST, /* game focus off, mouse let go */
INPUT_PLATFORM_PAUSE, /* pause the content */
INPUT_PLATFORM_UNPAUSE, /* resume it */
INPUT_PLATFORM_REQUEST_LAST
};
void input_driver_platform_request(enum input_platform_request req);
+1
View File
@@ -134,6 +134,7 @@
#include "../settings/settings_def_video_upscaler.h"
#include "../settings/settings_def_video_stereo.h"
#include "../settings/settings_def_video_headset.h"
#include "../settings/settings_def_video_screens.h"
#include "../settings/settings_def_rewind.h"
#include "../settings/settings_def_playlist_history.h"
#include "../settings/settings_def_menu_scroll.h"
+121
View File
@@ -7742,6 +7742,7 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_##T, us)
#include "../settings/settings_def_video_upscaler.h"
#include "../settings/settings_def_video_stereo.h"
#include "../settings/settings_def_video_headset.h"
#include "../settings/settings_def_video_screens.h"
#undef S_BOOL
#undef S_BOOL_NS
#undef S_BOOL_H
@@ -7923,6 +7924,86 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_OFF,
"Off"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_OFF,
"Off"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_EDIT,
"Edit Mode Only"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_ALWAYS,
"Always"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_OFF,
"Off"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_15,
"15°"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_30,
"30°"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_45,
"45°"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_90,
"90°"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_0,
"0 cm"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_1,
"1 cm"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_2,
"2 cm"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_3,
"3 cm"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_5,
"5 cm"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_10,
"10 cm"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_OFF,
"Off"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_LOW,
"Low"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_MEDIUM,
"Medium"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_HIGH,
"High"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_PAUSE_OFF,
"Keep Running"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_PAUSE_ON,
"Pause"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_PLACEMENT_FIXED,
"Fixed in Room"
@@ -12544,6 +12625,14 @@ MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_META_HEADSET_VIEW_TOGGLE,
"Cycles what the core is told the headset currently displays: Immersive (head-tracked), Stereoscopic (a 3D screen) or Flat. Returns to core's default on core closure."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_EDIT_SCREENS,
"Edit Headset Screens"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_META_HEADSET_EDIT_SCREENS,
"Starts or stops moving the headset's screens and menu with its controllers."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE,
"Show FPS (Toggle)"
@@ -34435,6 +34524,38 @@ MSG_HASH(
MSG_OPENXR_HEADSET_VIEW_FLAT,
"Flat"
)
MSG_HASH(
MSG_OPENXR_EDIT_ON,
"Editing headset screens: grip grabs, the stick moves and resizes, the trigger tilts it with your hand, the other hand's stick turns and tilts it, the main button resets."
)
MSG_HASH(
MSG_OPENXR_EDIT_OFF,
"Stopped editing headset screens."
)
MSG_HASH(
MSG_OPENXR_EDIT_UNSAVED,
"Stopped editing headset screens. Changes are not saved yet."
)
MSG_HASH(
MSG_OPENXR_EDIT_UNAVAILABLE,
"Edit Screens is unavailable: the headset's controllers cannot be used for it."
)
MSG_HASH(
MSG_OPENXR_EDIT_NO_CONTENT,
"Edit Screens needs content running: load content first."
)
MSG_HASH(
MSG_OPENXR_EDIT_TAG_LASER,
"Laser"
)
MSG_HASH(
MSG_OPENXR_EDIT_TAG_SCREEN,
"Screen %u"
)
MSG_HASH(
MSG_OPENXR_EDIT_TAG_MENU,
"Menu"
)
/* Lakka */
+23
View File
@@ -363,6 +363,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_video_filter_toggle, ME
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_headset_recenter, MENU_ENUM_SUBLABEL_INPUT_META_HEADSET_RECENTER)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_laser_pointer_toggle, MENU_ENUM_SUBLABEL_INPUT_META_LASER_POINTER_TOGGLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_headset_view_toggle, MENU_ENUM_SUBLABEL_INPUT_META_HEADSET_VIEW_TOGGLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_headset_edit_screens, MENU_ENUM_SUBLABEL_INPUT_META_HEADSET_EDIT_SCREENS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_fps_toggle, MENU_ENUM_SUBLABEL_INPUT_META_FPS_TOGGLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_statistics_toggle, MENU_ENUM_SUBLABEL_INPUT_META_STATISTICS_TOGGLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_ai_service, MENU_ENUM_SUBLABEL_INPUT_META_AI_SERVICE)
@@ -1853,6 +1854,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case RARCH_HEADSET_VIEW_TOGGLE:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_headset_view_toggle);
return 0;
case RARCH_HEADSET_EDIT_SCREENS:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_headset_edit_screens);
return 0;
case RARCH_FPS_TOGGLE:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_fps_toggle);
return 0;
@@ -2150,9 +2154,28 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
{ MENU_ENUM_LABEL_VIDEO_OPENXR_DISTANCE, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_DISTANCE },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_WIDTH, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_WIDTH },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_PLACEMENT, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_PLACEMENT },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_MENU_FOLLOW, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_MENU_FOLLOW },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_PERF_BOOST, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_PERF_BOOST },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_CONTROLLERS, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_CONTROLLERS },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_LASER, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_LASER },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_BEAM, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_LASER_BEAM },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_PAUSE, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_EDIT_PAUSE },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_EDIT_SNAP },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP_ANGLES, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_EDIT_SNAP_ANGLES },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_HINGE_GAP, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_EDIT_HINGE_GAP },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SMOOTHING, MENU_ENUM_SUBLABEL_VIDEO_OPENXR_EDIT_SMOOTHING },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_SELECTED },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_X, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_X },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Y, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_Y },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Z, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_Z },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_YAW, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_YAW },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_PITCH, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_PITCH },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_ROLL, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_ROLL },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_SCALE, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_SCALE },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_VISIBLE, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_VISIBLE },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_RESET },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET_ALL, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_RESET_ALL },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_EDIT, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_XR_EDIT },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_WINDOW_MODE, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_WINDOW_MODE },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_WINDOW_MONITOR, MENU_ENUM_SUBLABEL_VIDEO_SCREEN_WINDOW_MONITOR },
{ MENU_ENUM_LABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS, MENU_ENUM_SUBLABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS },
+58 -29
View File
@@ -9400,7 +9400,8 @@ unsigned menu_displaylist_build_list(
#ifndef HAVE_OPENXR
else if ( key == RARCH_HEADSET_RECENTER
|| key == RARCH_LASER_POINTER_TOGGLE
|| key == RARCH_HEADSET_VIEW_TOGGLE)
|| key == RARCH_HEADSET_VIEW_TOGGLE
|| key == RARCH_HEADSET_EDIT_SCREENS)
continue;
#endif
/* Show combo entries before normal binds */
@@ -12565,34 +12566,62 @@ unsigned menu_displaylist_build_list(
PARSE_ONLY_BOOL, false) == 0)
count++;
#endif
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIDEO_OPENXR_REFRESH_RATE,
PARSE_ONLY_UINT, false) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIDEO_OPENXR_PERF_BOOST,
PARSE_ONLY_BOOL, false) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIDEO_OPENXR_DISTANCE,
PARSE_ONLY_FLOAT, false) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIDEO_OPENXR_WIDTH,
PARSE_ONLY_FLOAT, false) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIDEO_OPENXR_PLACEMENT,
PARSE_ONLY_UINT, false) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIDEO_OPENXR_CONTROLLERS,
PARSE_ONLY_UINT, false) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIDEO_OPENXR_LASER,
PARSE_ONLY_UINT, false) == 0)
count++;
/* Edit mode needs the headset's output and controllers. */
#ifdef ANDROID
if (video_context_driver_headset_inline())
#else
if (settings->bools.video_openxr_enable)
#endif
{
static const menu_displaylist_settings_row_t edit_rows[] = {
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_EDIT, PARSE_ACTION, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP, PARSE_ONLY_BOOL, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP_ANGLES, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_HINGE_GAP, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SMOOTHING, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_PAUSE, PARSE_ONLY_BOOL, false },
};
count += menu_displaylist_parse_settings_rows(list, settings,
edit_rows, (unsigned)ARRAY_SIZE(edit_rows));
}
{
static const menu_displaylist_settings_row_t headset_rows[] = {
{ MENU_ENUM_LABEL_VIDEO_OPENXR_REFRESH_RATE, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_PERF_BOOST, PARSE_ONLY_BOOL, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_CONTROLLERS, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_LASER, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_BEAM, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_PLACEMENT, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_DISTANCE, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_WIDTH, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_OPENXR_MENU_FOLLOW, PARSE_ONLY_BOOL, false },
};
rarch_setting_t *screen = menu_setting_find_enum(
MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED);
count += menu_displaylist_parse_settings_rows(list, settings,
headset_rows, (unsigned)ARRAY_SIZE(headset_rows));
if (screen && screen->value.target.unsigned_integer)
{
static const menu_displaylist_settings_row_t screen_rows[] = {
{ MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED, PARSE_ONLY_UINT, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_VISIBLE, PARSE_ONLY_BOOL, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_X, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Y, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Z, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_YAW, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_PITCH, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_ROLL, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_SCALE, PARSE_ONLY_FLOAT, false },
{ MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET, PARSE_ACTION, false },
};
count += menu_displaylist_parse_settings_rows(list, settings,
screen_rows, (unsigned)ARRAY_SIZE(screen_rows));
}
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET_ALL,
PARSE_ACTION, false) == 0)
count++;
}
}
#endif
break;
+1
View File
@@ -543,6 +543,7 @@ struct menu_state
/* unsigned alignment */
unsigned input_dialog_kb_idx;
unsigned video_screen_selected;
menu_dialog_t dialog_st;
enum menu_input_dialog_kb_text_type input_dialog_kb_text_type;
#ifdef HAVE_RUNAHEAD
+373 -2
View File
@@ -354,9 +354,7 @@ SETTINGS_ACTION_SETTER(sel, action_select_handler_t)
SETTINGS_ACTION_SETTER(left, action_left_handler_t)
SETTINGS_ACTION_SETTER(right, action_right_handler_t)
SETTINGS_ACTION_SETTER(change, change_handler_t)
#ifdef ANDROID
SETTINGS_ACTION_SETTER(read, change_handler_t)
#endif
SETTINGS_ACTION_SETTER(repr, get_string_representation_t)
#define SETTINGS_ACTION_SET(field, s, fn) \
@@ -3952,6 +3950,134 @@ static size_t setting_get_string_representation_video_openxr_laser(
return 0;
}
static size_t setting_get_string_representation_video_openxr_laser_beam(
rarch_setting_t *setting, char *s, size_t len)
{
if (setting)
{
switch (*setting->value.target.unsigned_integer)
{
case VIDEO_OPENXR_LASER_BEAM_OFF:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_OFF), len);
case VIDEO_OPENXR_LASER_BEAM_EDIT:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_EDIT), len);
case VIDEO_OPENXR_LASER_BEAM_ALWAYS:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_ALWAYS),
len);
}
}
return 0;
}
static size_t setting_get_string_representation_video_openxr_edit_snap_angles(
rarch_setting_t *setting, char *s, size_t len)
{
if (setting)
{
switch (*setting->value.target.unsigned_integer)
{
case VIDEO_OPENXR_EDIT_SNAP_ANGLES_OFF:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_OFF),
len);
case VIDEO_OPENXR_EDIT_SNAP_ANGLES_15:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_15),
len);
case VIDEO_OPENXR_EDIT_SNAP_ANGLES_30:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_30),
len);
case VIDEO_OPENXR_EDIT_SNAP_ANGLES_45:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_45),
len);
case VIDEO_OPENXR_EDIT_SNAP_ANGLES_90:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_90),
len);
}
}
return 0;
}
static size_t setting_get_string_representation_video_openxr_edit_hinge_gap(
rarch_setting_t *setting, char *s, size_t len)
{
if (setting)
{
switch (*setting->value.target.unsigned_integer)
{
case VIDEO_OPENXR_EDIT_HINGE_GAP_0:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_0),
len);
case VIDEO_OPENXR_EDIT_HINGE_GAP_1:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_1),
len);
case VIDEO_OPENXR_EDIT_HINGE_GAP_2:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_2),
len);
case VIDEO_OPENXR_EDIT_HINGE_GAP_3:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_3),
len);
case VIDEO_OPENXR_EDIT_HINGE_GAP_5:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_5),
len);
case VIDEO_OPENXR_EDIT_HINGE_GAP_10:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_10),
len);
}
}
return 0;
}
static size_t setting_get_string_representation_video_openxr_edit_smoothing(
rarch_setting_t *setting, char *s, size_t len)
{
if (setting)
{
switch (*setting->value.target.unsigned_integer)
{
case VIDEO_OPENXR_EDIT_SMOOTHING_OFF:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_OFF),
len);
case VIDEO_OPENXR_EDIT_SMOOTHING_LOW:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_LOW),
len);
case VIDEO_OPENXR_EDIT_SMOOTHING_MEDIUM:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_MEDIUM),
len);
case VIDEO_OPENXR_EDIT_SMOOTHING_HIGH:
return strlcpy(s, msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_HIGH),
len);
}
}
return 0;
}
static size_t setting_get_string_representation_video_openxr_edit_pause(
rarch_setting_t *setting, char *s, size_t len)
{
if (!setting)
return 0;
return strlcpy(s, msg_hash_to_str(*setting->value.target.boolean
? MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_PAUSE_ON
: MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_PAUSE_OFF), len);
}
static size_t setting_get_string_representation_video_openxr_placement(
rarch_setting_t *setting, char *s, size_t len)
{
@@ -9376,6 +9502,10 @@ static const enum settings_list_type settings_list_build_order[] =
SETTINGS_LIST_MANUAL_CONTENT_SCAN
};
static bool setting_append_list_video_screen_settings(
rarch_setting_t **list, rarch_setting_info_t *list_info,
const char *parent_group);
static bool setting_append_list(settings_t *settings,
enum settings_list_type type, rarch_setting_t **list,
rarch_setting_info_t *list_info, const char *parent_group);
@@ -10267,6 +10397,194 @@ static size_t get_string_representation_input_mouse_index(
return _len;
}
static unsigned setting_video_screen_mask(void)
{
video_driver_state_t *video_st = video_state_get_ptr();
const video_views_map_t *map = &video_st->views_core;
unsigned i, mask = 0;
/* A frame without a map is screen 1. */
if (!video_st->views_presented)
return 1u;
for (i = 0; i < map->num_views && i < RETRO_VIDEO_VIEWS_MAX; i++)
if (map->views[i].screen < RETRO_VIDEO_VIEWS_MAX)
mask |= 1u << map->views[i].screen;
#ifdef HAVE_OPENXR
if ( video_driver_vr_content_active()
&& (video_driver_xr_headset() & VIDEO_XR_HEADSET_TRACKING))
{
int eyes[2];
if (video_views_eye_pair(map, eyes))
mask &= ~(1u << map->views[eyes[0]].screen);
}
#endif
return mask;
}
static unsigned setting_video_screen_bind(rarch_setting_t *setting)
{
settings_t *settings = config_get_ptr();
struct menu_state *menu_st = menu_state_get_ptr();
unsigned screen = menu_st->video_screen_selected;
unsigned mask = setting_video_screen_mask();
if (screen >= RETRO_VIDEO_VIEWS_MAX || !(mask & (1u << screen)))
{
for (screen = 0; screen < RETRO_VIDEO_VIEWS_MAX; screen++)
if (mask & (1u << screen))
break;
menu_st->video_screen_selected = screen < RETRO_VIDEO_VIEWS_MAX
? screen : 0;
}
if (setting->enum_idx == MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED)
{
setting->value.target.unsigned_integer = mask
? &menu_st->video_screen_selected : NULL;
return mask;
}
if (!mask)
return 0;
screen = menu_st->video_screen_selected;
setting->index_offset = screen;
switch (setting->enum_idx)
{
case MENU_ENUM_LABEL_VIDEO_SCREEN_XR_X:
setting->value.target.fraction = &settings->floats.video_screen_xr_x[screen];
break;
case MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Y:
setting->value.target.fraction = &settings->floats.video_screen_xr_y[screen];
break;
case MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Z:
setting->value.target.fraction = &settings->floats.video_screen_xr_z[screen];
break;
case MENU_ENUM_LABEL_VIDEO_SCREEN_XR_YAW:
setting->value.target.fraction = &settings->floats.video_screen_xr_yaw[screen];
break;
case MENU_ENUM_LABEL_VIDEO_SCREEN_XR_PITCH:
setting->value.target.fraction = &settings->floats.video_screen_xr_pitch[screen];
break;
case MENU_ENUM_LABEL_VIDEO_SCREEN_XR_ROLL:
setting->value.target.fraction = &settings->floats.video_screen_xr_roll[screen];
break;
case MENU_ENUM_LABEL_VIDEO_SCREEN_XR_SCALE:
setting->value.target.fraction = &settings->floats.video_screen_xr_scale[screen];
break;
case MENU_ENUM_LABEL_VIDEO_SCREEN_XR_VISIBLE:
setting->value.target.boolean = &settings->bools.video_screen_xr_visible[screen];
break;
}
return mask;
}
static void setting_video_screen_read(rarch_setting_t *setting)
{
setting_video_screen_bind(setting);
}
static void setting_video_screen_change(rarch_setting_t *setting)
{
if (setting->enum_idx != MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED)
config_get_ptr()->flags |= SETTINGS_FLG_MODIFIED;
}
static size_t setting_video_screen_repr(rarch_setting_t *setting,
char *s, size_t len)
{
if (!setting_video_screen_bind(setting))
return strlcpy(s, "", len);
if (setting->enum_idx == MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED)
return snprintf(s, len, "%u", menu_state_get_ptr()->video_screen_selected + 1);
if (setting->type == ST_FLOAT)
return setting_get_string_representation_st_float(setting, s, len);
if (setting->type == ST_BOOL)
return setting_get_string_representation_st_bool(setting, s, len);
return strlcpy(s, "", len);
}
static int setting_video_screen_step(rarch_setting_t *setting,
size_t idx, bool right)
{
unsigned mask = setting_video_screen_bind(setting);
if (!mask)
return -1;
if (setting->enum_idx == MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED)
{
struct menu_state *menu_st = menu_state_get_ptr();
unsigned screen = menu_st->video_screen_selected;
unsigned i;
for (i = 0; i < RETRO_VIDEO_VIEWS_MAX; i++)
{
screen = (screen + (right ? 1 : RETRO_VIDEO_VIEWS_MAX - 1))
% RETRO_VIDEO_VIEWS_MAX;
if (mask & (1u << screen))
break;
}
if (i == RETRO_VIDEO_VIEWS_MAX)
return -1;
menu_st->video_screen_selected = screen;
menu_st->flags |= MENU_ST_FLAG_PREVENT_POPULATE
| MENU_ST_FLAG_ENTRIES_NEED_REFRESH;
return 0;
}
if (setting->type == ST_BOOL)
return setting_bool_action_ok_default(setting, idx, false);
if (right)
return setting_fraction_action_right_default(setting, idx, false);
return setting_fraction_action_left_default(setting, idx, false);
}
static int setting_video_screen_left(rarch_setting_t *setting,
size_t idx, bool wraparound)
{
return setting_video_screen_step(setting, idx, false);
}
static int setting_video_screen_right(rarch_setting_t *setting,
size_t idx, bool wraparound)
{
return setting_video_screen_step(setting, idx, true);
}
static int setting_video_screen_start(rarch_setting_t *setting)
{
if (!setting_video_screen_bind(setting))
return -1;
if (setting->enum_idx == MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED)
{
menu_state_get_ptr()->video_screen_selected = RETRO_VIDEO_VIEWS_MAX;
setting_video_screen_bind(setting);
return 0;
}
return setting_generic_action_start_default(setting);
}
static int setting_video_screen_reset(rarch_setting_t *setting,
size_t idx, bool wraparound)
{
settings_t *settings = config_get_ptr();
struct menu_state *menu_st = menu_state_get_ptr();
if (setting->enum_idx == MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET_ALL)
{
config_reset_video_screen_xr(settings, 0, RETRO_VIDEO_VIEWS_MAX);
config_reset_video_menu_xr(settings);
}
else
{
if (!setting_video_screen_bind(setting))
return -1;
config_reset_video_screen_xr(settings, menu_st->video_screen_selected, 1);
}
settings->flags |= SETTINGS_FLG_MODIFIED;
menu_st->flags |= MENU_ST_FLAG_PREVENT_POPULATE
| MENU_ST_FLAG_ENTRIES_NEED_REFRESH;
return 0;
}
static int setting_video_screen_edit(rarch_setting_t *setting,
size_t idx, bool wraparound)
{
command_event(CMD_EVENT_HEADSET_EDIT_SCREENS, NULL);
return 0;
}
static void general_read_handler(rarch_setting_t *setting)
{
settings_t *settings = config_get_ptr();
@@ -13495,6 +13813,10 @@ static const setting_desc_t upscaler_desc[] = {
};
#endif
static const setting_desc_t video_screens_desc[] = {
#include "../settings/settings_def_video_screens.h"
};
static const setting_desc_t headset_desc[] = {
/* GENERATED: rows come from settings_def_video_headset.h in order. */
#include "../settings/settings_def_video_headset.h"
@@ -16502,6 +16824,7 @@ static void settings_build_video(
setting_append_list_video_screen_windows(settings, list, list_info,
&group_info, &subgroup_info, parent_group);
#endif
setting_append_list_video_screen_settings(list, list_info, parent_group);
END_SUB_GROUP(list, list_info, parent_group);
@@ -19644,6 +19967,54 @@ static void settings_build_desc_group(
GROUP_END();
}
static bool setting_append_list_video_screen_settings(
rarch_setting_t **list, rarch_setting_info_t *list_info,
const char *parent_group)
{
settings_t *settings = config_get_ptr();
rarch_setting_group_info_t group_info;
rarch_setting_group_info_t subgroup_info;
unsigned first = list_info->index;
int i;
group_info.name = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_SETTINGS);
subgroup_info.name = "Stereo 3D & Screens";
CONFIG_UINT(list, list_info, &menu_state_get_ptr()->video_screen_selected,
MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED,
MENU_ENUM_LABEL_VALUE_VIDEO_SCREEN_SELECTED, 0,
&group_info, &subgroup_info, parent_group,
setting_video_screen_change, setting_video_screen_read);
settings_list_add_desc(list, list_info, settings, video_screens_desc + 1,
ARRAY_SIZE(video_screens_desc) - 1, &group_info, &subgroup_info,
parent_group);
for (i = first; i < list_info->index; i++)
{
rarch_setting_t *setting = &(*list)[i];
SETTINGS_ACTION_SET(read, setting, setting_video_screen_read)
SETTINGS_ACTION_SET(change, setting, setting_video_screen_change)
if (setting->enum_idx == MENU_ENUM_LABEL_VIDEO_SCREEN_XR_EDIT)
{
SETTINGS_ACTION_SET(ok, setting, setting_video_screen_edit)
SETTINGS_ACTION_SET(sel, setting, setting_video_screen_edit)
}
else if (setting->type == ST_ACTION)
{
SETTINGS_ACTION_SET(ok, setting, setting_video_screen_reset)
SETTINGS_ACTION_SET(sel, setting, setting_video_screen_reset)
}
else
{
SETTINGS_ACTION_SET(repr, setting, setting_video_screen_repr)
SETTINGS_ACTION_SET(left, setting, setting_video_screen_left)
SETTINGS_ACTION_SET(right, setting, setting_video_screen_right)
SETTINGS_ACTION_SET(ok, setting, setting_video_screen_right)
SETTINGS_ACTION_SET(sel, setting, setting_video_screen_right)
SETTINGS_ACTION_SET(start, setting, setting_video_screen_start)
}
setting_video_screen_bind(setting);
}
return true;
}
static const settings_build_entry_t settings_build_registry[] = {
{ SETTINGS_LIST_MAIN_MENU, settings_build_main_menu, NULL, 0, MSG_UNKNOWN, MSG_UNKNOWN, MSG_UNKNOWN },
{ SETTINGS_LIST_DRIVERS, settings_build_drivers, NULL, 0, MSG_UNKNOWN, MSG_UNKNOWN, MSG_UNKNOWN },
+31
View File
@@ -759,6 +759,14 @@ enum msg_hash_enums
MSG_OPENXR_HEADSET_VIEW_IMMERSIVE,
MSG_OPENXR_HEADSET_VIEW_STEREOSCOPIC,
MSG_OPENXR_HEADSET_VIEW_FLAT,
MSG_OPENXR_EDIT_ON,
MSG_OPENXR_EDIT_OFF,
MSG_OPENXR_EDIT_UNSAVED,
MSG_OPENXR_EDIT_UNAVAILABLE,
MSG_OPENXR_EDIT_NO_CONTENT,
MSG_OPENXR_EDIT_TAG_LASER,
MSG_OPENXR_EDIT_TAG_SCREEN,
MSG_OPENXR_EDIT_TAG_MENU,
MSG_IOS_TOUCH_MOUSE_ENABLED,
MSG_IOS_TOUCH_MOUSE_DISABLED,
@@ -2763,6 +2771,7 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_RECENTER,
MENU_ENUM_LABEL_VALUE_INPUT_META_LASER_POINTER_TOGGLE,
MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_VIEW_TOGGLE,
MENU_ENUM_LABEL_VALUE_INPUT_META_HEADSET_EDIT_SCREENS,
MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE,
MENU_ENUM_LABEL_VALUE_INPUT_META_STATISTICS_TOGGLE,
MENU_ENUM_LABEL_VALUE_INPUT_META_AI_SERVICE,
@@ -2890,6 +2899,7 @@ enum msg_hash_enums
MENU_ENUM_SUBLABEL_INPUT_META_HEADSET_RECENTER,
MENU_ENUM_SUBLABEL_INPUT_META_LASER_POINTER_TOGGLE,
MENU_ENUM_SUBLABEL_INPUT_META_HEADSET_VIEW_TOGGLE,
MENU_ENUM_SUBLABEL_INPUT_META_HEADSET_EDIT_SCREENS,
MENU_ENUM_SUBLABEL_INPUT_META_FPS_TOGGLE,
MENU_ENUM_SUBLABEL_INPUT_META_STATISTICS_TOGGLE,
MENU_ENUM_SUBLABEL_INPUT_META_AI_SERVICE,
@@ -9403,6 +9413,7 @@ enum msg_hash_enums
#include "settings/settings_def_video_upscaler.h"
#include "settings/settings_def_video_stereo.h"
#include "settings/settings_def_video_headset.h"
#include "settings/settings_def_video_screens.h"
#undef S_BOOL
#undef S_BOOL_NS
#undef S_UINT
@@ -9493,6 +9504,26 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_AUTO,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_ALWAYS,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_OFF,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_OFF,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_EDIT,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_LASER_BEAM_ALWAYS,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_OFF,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_15,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_30,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_45,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SNAP_ANGLES_90,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_0,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_1,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_2,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_3,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_5,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_HINGE_GAP_10,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_OFF,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_LOW,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_MEDIUM,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_SMOOTHING_HIGH,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_PAUSE_OFF,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_EDIT_PAUSE_ON,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_PLACEMENT_FIXED,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_PLACEMENT_FOLLOW,
MENU_ENUM_LABEL_VALUE_VIDEO_OPENXR_PLACEMENT_LAZY,
+38
View File
@@ -1400,9 +1400,28 @@ extern const struct msg_hash_us_lbl_blob_s msg_hash_us_lbl_blob;
#define MENU_ENUM_LABEL_VIDEO_OPENXR_DISTANCE_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_DISTANCE)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_WIDTH_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_WIDTH)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_PLACEMENT_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_PLACEMENT)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_MENU_FOLLOW_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_MENU_FOLLOW)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_PERF_BOOST_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_PERF_BOOST)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_CONTROLLERS_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_CONTROLLERS)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_LASER)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_BEAM_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_BEAM)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_PAUSE_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_PAUSE)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP_ANGLES_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP_ANGLES)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_HINGE_GAP_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_HINGE_GAP)
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SMOOTHING_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SMOOTHING)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_X_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_X)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Y_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Y)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Z_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Z)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_YAW_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_YAW)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_PITCH_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_PITCH)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_ROLL_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_ROLL)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_SCALE_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_SCALE)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_VISIBLE_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_VISIBLE)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET_ALL_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET_ALL)
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_EDIT_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SCREEN_XR_EDIT)
#define MENU_ENUM_LABEL_REWIND_ENABLE_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_REWIND_ENABLE)
#define MENU_ENUM_LABEL_REWIND_GRANULARITY_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_REWIND_GRANULARITY)
#define MENU_ENUM_LABEL_PLAYLIST_SHOW_HISTORY_ICONS_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_PLAYLIST_SHOW_HISTORY_ICONS)
@@ -3145,9 +3164,28 @@ extern const struct msg_hash_us_lbl_blob_s msg_hash_us_lbl_blob;
#define MENU_ENUM_LABEL_VIDEO_OPENXR_DISTANCE_STR "video_openxr_distance"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_WIDTH_STR "video_openxr_width"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_PLACEMENT_STR "video_openxr_placement"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_MENU_FOLLOW_STR "video_openxr_menu_follow"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_PERF_BOOST_STR "video_openxr_perf_boost"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_CONTROLLERS_STR "video_openxr_controllers"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_STR "video_openxr_laser"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_BEAM_STR "video_openxr_laser_beam"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_PAUSE_STR "video_openxr_edit_pause"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP_STR "video_openxr_edit_snap"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SNAP_ANGLES_STR "video_openxr_edit_snap_angles"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_HINGE_GAP_STR "video_openxr_edit_hinge_gap"
#define MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_SMOOTHING_STR "video_openxr_edit_smoothing"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_SELECTED_STR "video_screen_selected"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_X_STR "video_screen_xr_x"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Y_STR "video_screen_xr_y"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_Z_STR "video_screen_xr_z"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_YAW_STR "video_screen_xr_yaw"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_PITCH_STR "video_screen_xr_pitch"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_ROLL_STR "video_screen_xr_roll"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_SCALE_STR "video_screen_xr_scale"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_VISIBLE_STR "video_screen_xr_visible"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET_STR "video_screen_xr_reset"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_RESET_ALL_STR "video_screen_xr_reset_all"
#define MENU_ENUM_LABEL_VIDEO_SCREEN_XR_EDIT_STR "video_screen_xr_edit"
#define MENU_ENUM_LABEL_REWIND_ENABLE_STR "rewind_enable"
#define MENU_ENUM_LABEL_REWIND_GRANULARITY_STR "rewind_granularity"
#define MENU_ENUM_LABEL_PLAYLIST_SHOW_HISTORY_ICONS_STR "playlist_show_history_icons"
+26 -1
View File
@@ -1785,7 +1785,9 @@ void drivers_init(
video_st->frame_time_count = 0;
#ifdef HAVE_OPENXR
/* Before video makes a headset session. */
/* A video reinit ends edit mode; then video makes a headset
* session. */
input_openxr_edit_end();
input_openxr_register();
#endif
video_driver_lock_new();
@@ -6534,6 +6536,10 @@ bool command_event(enum event_command cmd, void *data)
}
break;
case CMD_EVENT_HEADSET_RECENTER:
#ifdef HAVE_OPENXR
/* A grab ends first, where the hand left its item. */
input_openxr_edit_release();
#endif
video_driver_headset_recenter();
break;
case CMD_EVENT_HEADSET_VIEW_TOGGLE:
@@ -6596,6 +6602,25 @@ bool command_event(enum event_command cmd, void *data)
#endif
}
break;
case CMD_EVENT_HEADSET_EDIT_SCREENS:
#ifdef HAVE_OPENXR
/* Edit mode starts here, between frames, as its pause draws a
* frame; the menu closes first. */
if (input_openxr_edit_toggle())
{
#ifdef HAVE_MENU
if (menu_st->flags & MENU_ST_FLAG_ALIVE)
retroarch_menu_running_finished(false);
#endif
input_openxr_edit_begin(
settings->bools.video_openxr_edit_pause,
settings->bools.video_openxr_edit_snap,
settings->uints.video_openxr_edit_snap_angles,
settings->uints.video_openxr_edit_hinge_gap,
settings->uints.video_openxr_edit_smoothing);
}
#endif
break;
case CMD_EVENT_NONE:
return false;
+3
View File
@@ -647,6 +647,9 @@
# Cycles what the core is told the headset shows: Immersive, Stereoscopic or Flat.
# input_headset_view_toggle = v
# Starts or stops moving the headset's screens and menu with its controllers.
# input_headset_edit_screens =
# Enable other hotkeys.
# If this hotkey is bound to either keyboard, joybutton or joyaxis,
# all other hotkeys will be disabled unless this hotkey is also held at the same time.
+3
View File
@@ -8853,6 +8853,9 @@ static enum runloop_state_enum runloop_check_state(
/* Check headset view hotkey */
HOTKEY_CHECK(RARCH_HEADSET_VIEW_TOGGLE, CMD_EVENT_HEADSET_VIEW_TOGGLE, true, NULL);
/* Check headset edit hotkey: here, so it works in the menu too */
HOTKEY_CHECK(RARCH_HEADSET_EDIT_SCREENS, CMD_EVENT_HEADSET_EDIT_SCREENS, true, NULL);
/* Check netplay host hotkey */
HOTKEY_CHECK(RARCH_NETPLAY_HOST_TOGGLE, CMD_EVENT_NETPLAY_HOST_TOGGLE, true, NULL);
+2 -1
View File
@@ -6,7 +6,8 @@ CFLAGS += -O1 -Wall
all: defaults_target_test
defaults_target_test: defaults_target_test.c $(ROOT)/configuration.c $(ROOT)/configuration.h
defaults_target_test: defaults_target_test.c $(ROOT)/configuration.c $(ROOT)/configuration.h \
$(ROOT)/gfx/video_views.c $(ROOT)/gfx/video_views.h
$(CC) $(CPPFLAGS) $(CFLAGS) -fwhole-program $< $(LDFLAGS) -lm -o $@
check: defaults_target_test
@@ -4,6 +4,7 @@
* defaults without ever swapping the pointer other threads read. */
#include "../../../configuration.c"
#include "../../../libretro-common/compat/compat_strl.c"
#include "../../../gfx/video_views.c"
#include <stdio.h>
@@ -40,19 +40,32 @@ static void *test_realloc(void *p, size_t size)
return realloc(p, size);
}
/* Reflect the stock shader's Source sampler, with no uniform buffers. */
/* Reflect the stock shader's Source sampler, with no uniform buffers.
* With replay_reflect each pass also reads PassFeedback0 and
* OriginalHistory1, on units 1 and 2. */
static slang_texture_semantic_meta texture_meta[SLANG_NUM_TEXTURE_SEMANTICS];
static slang_texture_semantic_meta history_meta[2];
static bool replay_reflect;
static bool test_reflection_init(slang_reflection *r)
{
unsigned i;
memset(r, 0, sizeof(*r));
memset(texture_meta, 0, sizeof(texture_meta));
memset(history_meta, 0, sizeof(history_meta));
for (i = 0; i < SLANG_NUM_TEXTURE_SEMANTICS; i++)
{
r->semantic_textures[i].data = &texture_meta[i];
r->semantic_textures[i].size = 1;
memset(&texture_meta[i].location, -1, sizeof(texture_meta[i].location));
}
if (replay_reflect)
{
r->semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].data =
history_meta;
r->semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].size = 2;
memset(&history_meta[0].location, -1, sizeof(history_meta[0].location));
memset(&history_meta[1].location, -1, sizeof(history_meta[1].location));
}
return true;
}
static void test_reflection_free(slang_reflection *r)
@@ -61,6 +74,13 @@ static bool test_reflect(const struct slang_blob *v,
const struct slang_blob *f, slang_reflection *r)
{
r->semantic_textures[SLANG_TEXTURE_SEMANTIC_SOURCE].data[0].texture = true;
if (replay_reflect)
{
r->semantic_textures[SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK].data[0].texture = true;
r->semantic_textures[SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK].data[0].binding = 1;
r->semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].data[1].texture = true;
r->semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].data[1].binding = 2;
}
return v && v->nwords && f && f->nwords;
}
static unsigned test_miplevels(unsigned w, unsigned h)
@@ -79,6 +99,18 @@ static unsigned next_texture = 1;
static int live_textures, live_framebuffers, live_buffers, live_shaders, live_programs;
static unsigned mipmaps, trilinear, storage_levels, draws;
static GLuint bound_texture, mip_texture;
/* What each draw saw: the textures on units 0-2, each texture's draws
* into it so far (its generation), and what draws write. */
#define TEX_NAMES 4096
static unsigned active_unit;
static GLuint unit_texture[3];
static unsigned tex_gen[TEX_NAMES];
static GLuint fb_texture[TEX_NAMES];
static GLuint bound_fb;
static GLuint seen_texture[3];
static unsigned seen_gen[3];
static uint64_t seen_count;
static const uint64_t *watched_count;
static void APIENTRY mock_gen_textures(GLsizei n, GLuint *names)
{
GLsizei i;
@@ -88,7 +120,12 @@ static void APIENTRY mock_gen_textures(GLsizei n, GLuint *names)
static void APIENTRY mock_delete_textures(GLsizei n, const GLuint *names)
{ (void)names; live_textures -= n; }
static void APIENTRY mock_bind_texture(GLenum target, GLuint name)
{ (void)target; bound_texture = name; }
{
(void)target;
bound_texture = name;
if (active_unit < 3)
unit_texture[active_unit] = name;
}
static void APIENTRY mock_tex_parameter(GLenum target, GLenum pname, GLint value)
{
(void)target;
@@ -97,7 +134,24 @@ static void APIENTRY mock_tex_parameter(GLenum target, GLenum pname, GLint value
trilinear++;
}
static void APIENTRY mock_draw(GLenum mode, GLint first, GLsizei count)
{ (void)mode; (void)first; (void)count; draws++; }
{
unsigned u;
GLuint target = bound_fb < TEX_NAMES ? fb_texture[bound_fb] : 0;
(void)mode;
(void)first;
(void)count;
draws++;
for (u = 0; u < 3; u++)
{
seen_texture[u] = unit_texture[u];
seen_gen[u] = unit_texture[u] < TEX_NAMES
? tex_gen[unit_texture[u]] : 0;
}
if (watched_count)
seen_count = *watched_count;
if (target && target < TEX_NAMES)
tex_gen[target]++;
}
#define slang_reflection_init test_reflection_init
#define slang_reflection_free test_reflection_free
@@ -142,18 +196,26 @@ static void APIENTRY mock_gen_framebuffers(GLsizei n, GLuint *names)
static void APIENTRY mock_delete_framebuffers(GLsizei n, const GLuint *names)
{ (void)names; live_framebuffers -= n; }
static void APIENTRY mock_bind_framebuffer(GLenum target, GLuint name)
{ (void)target; (void)name; }
{ (void)target; bound_fb = name; }
static GLenum APIENTRY mock_framebuffer_status(GLenum target)
{ (void)target; return GL_FRAMEBUFFER_COMPLETE; }
static void APIENTRY mock_framebuffer_texture(GLenum target, GLenum attach,
GLenum textarget, GLuint texture, GLint level)
{ (void)target; (void)attach; (void)textarget; (void)texture; (void)level; }
{
(void)target;
(void)attach;
(void)textarget;
(void)level;
if (bound_fb < TEX_NAMES)
fb_texture[bound_fb] = texture;
}
static void APIENTRY mock_storage(GLenum target, GLsizei levels, GLenum format,
GLsizei width, GLsizei height)
{ (void)target; (void)format; (void)width; (void)height; storage_levels = levels; }
static void APIENTRY mock_mipmap(GLenum target)
{ (void)target; mipmaps++; mip_texture = bound_texture; }
static void APIENTRY mock_active_texture(GLenum target) { (void)target; }
static void APIENTRY mock_active_texture(GLenum target)
{ active_unit = target - GL_TEXTURE0; }
static void APIENTRY mock_attrib(GLuint index) { (void)index; }
static void APIENTRY mock_attrib_pointer(GLuint index, GLint size, GLenum type,
GLboolean normalized, GLsizei stride, const void *p)
@@ -231,6 +293,7 @@ static struct slang_blob *stock_blob(const uint32_t *words, size_t len)
/* @passes passes binding @vs/@fs by reference, as a preset's passes do
* once the blob table hands the same stages to each of them, with the
* text rslc printed for them. */
static bool chain_replay;
static struct gl3_filter_chain *chain_of(unsigned passes,
struct slang_blob *vs, struct slang_blob *fs)
{
@@ -239,6 +302,7 @@ static struct gl3_filter_chain *chain_of(unsigned passes,
unsigned i;
if (!chain)
return NULL;
chain->replay = chain_replay;
pass_info.scale_type_x = GLSLANG_FILTER_CHAIN_SCALE_VIEWPORT;
pass_info.scale_type_y = GLSLANG_FILTER_CHAIN_SCALE_VIEWPORT;
pass_info.scale_x = 1.0f;
@@ -405,6 +469,118 @@ static void test_shrink_chain(void)
}
}
/* The history copy, drawn into its framebuffer's texture. */
extern void (*test_framebuffer_copied)(unsigned fb_id);
static void framebuffer_copied(unsigned fb_id)
{
if (fb_id < TEX_NAMES && fb_texture[fb_id] < TEX_NAMES)
tex_gen[fb_texture[fb_id]]++;
}
/* One frame through @chain: its input @in, count @count, every pass,
* and the frame ended. */
static void replay_frame(struct gl3_filter_chain *chain, GLuint in,
uint64_t count)
{
struct gl3_filter_chain_texture texture;
struct gl3_viewport vp;
float mvp[16];
memset(&texture, 0, sizeof(texture));
texture.image = in;
texture.dims = VIDEO_SCALE_PACK(64, 32);
texture.format = GL_RGBA8;
vp.pos = 0;
vp.dims = VIDEO_SCALE_PACK(64, 32);
gl3_build_default_matrix(mvp);
gl3_filter_chain_set_frame_count(chain, count);
gl3_filter_chain_set_input_texture(chain, &texture);
gl3_filter_chain_build_offscreen_passes(chain, &vp);
mock_bind_framebuffer(GL_FRAMEBUFFER, 0);
gl3_filter_chain_build_viewport_pass(chain, &vp, mvp);
gl3_filter_chain_end_frame(chain);
}
/* A replay of the last frame's final pass, in a later frame and at
* another size, reads what that pass read: the same textures - the
* first pass's output, its feedback, the history - with nothing drawn
* into them since, and that frame's count. Feedback does not swap. The
* history keeps a spare slot, so the frame's end did not write over the
* one the pass read. */
static void test_replay(void)
{
GLuint texture[3];
unsigned gen[3], u;
struct gl3_filter_chain *chain;
struct gl3_framebuffer *fb, *feedback;
struct gl3_viewport vp;
float mvp[16];
uint32_t words[] = { 0x07230203u, 0x00010000u };
struct slang_blob *vs = stock_blob(words, sizeof(words));
struct slang_blob *fs = stock_blob(words, sizeof(words));
/* Created to replay: one history texture more than read. Any other
* chain keeps only what its passes read. */
replay_reflect = true;
chain = chain_of(2, vs, fs);
CHECK(chain && chain->num_original_history == 1,
"a chain made without replay keeps %u history framebuffers",
chain ? (unsigned)chain->num_original_history : 0);
gl3_chain_free(chain);
chain_replay = true;
chain = chain_of(2, vs, fs);
chain_replay = false;
replay_reflect = false;
slang_blob_unref(vs);
slang_blob_unref(fs);
CHECK(chain, "replay chain not built");
if (!chain)
return;
watched_count = &chain->passes[1]->frame_count;
test_framebuffer_copied = framebuffer_copied;
CHECK(chain->num_original_history == 2
&& chain->common.num_original_history == 1,
"history: %u framebuffers for %u read",
(unsigned)chain->num_original_history,
(unsigned)chain->common.num_original_history);
CHECK(!gl3_filter_chain_replay_ready(chain),
"a chain with no frame drawn is ready to replay");
replay_frame(chain, 0x1001, 1);
replay_frame(chain, 0x1002, 2);
for (u = 0; u < 3; u++)
{
texture[u] = seen_texture[u];
gen[u] = seen_gen[u];
}
CHECK(seen_count == 2, "frame 2 drew with count %u", (unsigned)seen_count);
CHECK(gl3_filter_chain_replay_ready(chain), "no replay after a frame");
fb = chain->passes[0]->framebuffer;
feedback = chain->passes[0]->framebuffer_feedback;
gl3_filter_chain_set_frame_count(chain, 3);
vp.pos = 0;
vp.dims = VIDEO_SCALE_PACK(80, 40);
gl3_build_default_matrix(mvp);
mock_bind_framebuffer(GL_FRAMEBUFFER, 0);
draws = 0;
gl3_filter_chain_build_viewport_pass_replay(chain, &vp, mvp);
CHECK(draws == 1, "replay drew %u passes", draws);
for (u = 0; u < 3; u++)
CHECK(seen_texture[u] == texture[u] && seen_gen[u] == gen[u],
"replay unit %u: texture %u gen %u, want %u gen %u", u,
seen_texture[u], seen_gen[u], texture[u], gen[u]);
CHECK(seen_count == 2, "replay drew with count %u, want 2",
(unsigned)seen_count);
CHECK(chain->passes[1]->frame_count == 3,
"replay left the count at %u",
(unsigned)chain->passes[1]->frame_count);
CHECK(chain->passes[0]->framebuffer == fb
&& chain->passes[0]->framebuffer_feedback == feedback,
"replay swapped the feedback buffers");
watched_count = NULL;
test_framebuffer_copied = NULL;
gl3_chain_free(chain);
}
int main(void)
{
unsigned allocs, k;
@@ -466,6 +642,7 @@ int main(void)
test_shrink_chain();
test_shared_programs();
test_unread_luts();
test_replay();
if (failures)
{
+7 -1
View File
@@ -42,7 +42,13 @@ void input_driver_set_shader_uses_sensors(int on) { (void)on; }
int input_driver_read_sensor_snapshot(void *s) { (void)s; return 0; }
int video_shader_load_preset_into_shader(const char *p, void *s)
{ (void)p; (void)s; return 0; }
void gl3_framebuffer_copy(void) { }
/* A test that follows what is drawn into framebuffers hooks this. */
void (*test_framebuffer_copied)(unsigned fb_id);
void gl3_framebuffer_copy(unsigned fb_id)
{
if (test_framebuffer_copied)
test_framebuffer_copied(fb_id);
}
void gl3_framebuffer_copy_partial(void) { }
uint32_t gl3_get_cross_compiler_target_version(void) { return 430; }
int gl3_spirv_binary_supported(void) { return 0; }
+1
View File
@@ -0,0 +1 @@
.build/
+27
View File
@@ -0,0 +1,27 @@
CC ?= cc
PYTHON ?= python3
BUILD_DIR ?= .build
REPO_ROOT := ../../..
TARGET := $(BUILD_DIR)/gl3_xr_draw_test$(EXE)
CFLAGS += -Wall -Wextra -pedantic -std=c89 -Werror=declaration-after-statement \
-Werror=implicit-function-declaration -Wno-long-long \
-I$(REPO_ROOT) -I$(REPO_ROOT)/libretro-common/include \
-I$(REPO_ROOT)/gfx/include -I$(BUILD_DIR)
all: $(TARGET)
$(BUILD_DIR)/driver_code.inc: extract.py $(REPO_ROOT)/gfx/drivers/gl3.c
@mkdir -p $(BUILD_DIR)
$(PYTHON) extract.py $@
$(TARGET): gl3_xr_draw_test.c $(BUILD_DIR)/driver_code.inc
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
check: $(TARGET)
$(RUNNER) $(TARGET)
clean:
rm -f $(BUILD_DIR)/driver_code.inc $(TARGET)
.PHONY: all check clean
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env python3
"""Exercise GL3's headset draw: what it draws straight into the
runtime's textures, and when a chain ends a frame."""
from pathlib import Path
import re
import sys
root = Path(__file__).resolve().parents[3]
driver = (root / 'gfx/drivers/gl3.c').read_text()
def function(source, name):
masked = re.sub(r'/\*.*?\*/|//[^\n]*|"(?:\\.|[^"\\])*"',
lambda m: ' ' * len(m[0]), source, flags=re.S)
match = re.search(r'static (?:bool|void) ' + re.escape(name)
+ r'\([^;]*?\)\s*\{', masked)
if not match:
raise RuntimeError('function missing: ' + name)
end = masked.index('{', match.start()) + 1
depth = 1
while depth:
depth += (masked[end] == '{') - (masked[end] == '}')
end += 1
return source[match.start():end]
parts = [function(driver, name) for name in
('gl3_xr_same', 'gl3_xr_keep', 'gl3_xr_draw_screen',
'gl3_xr_draw')]
Path(sys.argv[1]).write_text('\n\n'.join(parts) + '\n')
+392
View File
@@ -0,0 +1,392 @@
/* GL3's headset draw, copied out of gfx/drivers/gl3.c, with GL, the
* runtime's textures and the chain's passes stubbed: each screen is
* drawn straight into the texture the runtime gives, a new frame ends
* the chain's frame, and a repeat draws a screen again only when it
* moved or changed size, by replaying the last frame's final pass. */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <boolean.h>
#include "gfx/video_views.h"
#include "gfx/video_defines.h"
#include "gfx/video_xr.h"
typedef unsigned GLuint;
typedef int GLint;
typedef unsigned GLenum;
typedef unsigned char GLboolean;
typedef float GLfloat;
typedef int GLsizei;
typedef unsigned GLbitfield;
#define GL_FRAMEBUFFER 0x8D40
#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6
#define GL_VIEWPORT 0x0BA2
#define GL_SCISSOR_TEST 0x0C11
#define GL_BLEND 0x0BE2
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_TEXTURE_2D 0x0DE1
#define GL_COLOR_BUFFER_BIT 0x00004000
struct gl3_filter_chain_texture
{
GLuint image;
unsigned dims;
unsigned padded_dims;
unsigned origin;
GLenum format;
};
struct gl3_viewport
{
unsigned pos;
unsigned dims;
};
typedef struct gl3_filter_chain gl3_filter_chain_t;
typedef struct gl_openxr gl_openxr_t;
typedef struct
{
video_views_map_t views;
bool frame_repeat;
} video_frame_info_t;
typedef struct gl3
{
struct
{
GLuint tex[RETRO_VIDEO_VIEWS_MAX];
unsigned tex_dims[RETRO_VIDEO_VIEWS_MAX];
GLenum tex_format[RETRO_VIDEO_VIEWS_MAX];
} views;
struct
{
gl_openxr_t *host;
video_xr_quad_set_t set;
struct
{
video_xr_slot_t info;
video_xr_quad_t quads[4];
unsigned num_quads;
bool published;
} shown[VIDEO_XR_MAX_SLOTS];
float mvp[16];
GLuint fbo;
bool proj_shown;
} xr;
unsigned rotation_raw;
} gl3_t;
static unsigned failures;
/* The runtime's textures are 0x100 + slot * 2 + layer; any other name
* attached is an image of the driver's own. */
#define RUNTIME_TEX(s, l) (0x100u + (s) * 2u + (l))
static unsigned offscreen_passes, viewport_passes, replays;
static unsigned frames_ended, last_pass_dims;
static bool chain_drawn = true;
static unsigned runtime_attaches, own_attaches;
static unsigned published[VIDEO_XR_MAX_SLOTS];
static video_xr_quad_set_t planned;
static void glGetIntegerv(GLenum pname, GLint *v)
{
(void)pname;
v[0] = 0;
if (pname == GL_VIEWPORT)
v[1] = v[2] = v[3] = 0;
}
static GLboolean glIsEnabled(GLenum cap) { (void)cap; return 0; }
static void glEnable(GLenum cap) { (void)cap; }
static void glDisable(GLenum cap) { (void)cap; }
static void glGenFramebuffers(GLsizei n, GLuint *fbos)
{
(void)n;
fbos[0] = 1;
}
static void glBindFramebuffer(GLenum target, GLuint fbo)
{
(void)target;
(void)fbo;
}
static void glFramebufferTexture2D(GLenum target, GLenum att,
GLenum textarget, GLuint tex, GLint level)
{
(void)target;
(void)att;
(void)textarget;
(void)level;
if (!tex)
return;
if (tex >= RUNTIME_TEX(0, 0)
&& tex <= RUNTIME_TEX(VIDEO_XR_MAX_SLOTS - 1, 1))
runtime_attaches++;
else
own_attaches++;
}
static void glViewport(GLint x, GLint y, GLsizei w, GLsizei h)
{
(void)x;
(void)y;
(void)w;
(void)h;
}
static void glClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
{
(void)r;
(void)g;
(void)b;
(void)a;
}
static void glClear(GLbitfield mask) { (void)mask; }
static void gl3_filter_chain_set_input_texture(gl3_filter_chain_t *chain,
const struct gl3_filter_chain_texture *in)
{
(void)chain;
(void)in;
}
static void gl3_filter_chain_build_offscreen_passes(
gl3_filter_chain_t *chain, const struct gl3_viewport *vp)
{
(void)chain;
(void)vp;
offscreen_passes++;
}
static bool gl3_filter_chain_replay_ready(const gl3_filter_chain_t *chain)
{
(void)chain;
return chain_drawn;
}
static void gl3_filter_chain_build_viewport_pass_replay(
gl3_filter_chain_t *chain, const struct gl3_viewport *vp,
const float *mvp)
{
(void)chain;
(void)mvp;
replays++;
last_pass_dims = vp->dims;
}
static void gl3_filter_chain_build_viewport_pass(gl3_filter_chain_t *chain,
const struct gl3_viewport *vp, const float *mvp)
{
(void)chain;
(void)mvp;
viewport_passes++;
last_pass_dims = vp->dims;
}
static void gl3_filter_chain_end_frame(gl3_filter_chain_t *chain)
{
(void)chain;
frames_ended++;
}
static void gl3_xr_plan(gl3_t *gl, const video_frame_info_t *video_info,
bool views, bool eyes, unsigned frame_dims, unsigned src_dims,
unsigned ui_dims)
{
(void)video_info;
(void)views;
(void)eyes;
(void)frame_dims;
(void)src_dims;
(void)ui_dims;
gl->xr.set = planned;
}
bool video_xr_shrinks(unsigned image_dims, unsigned source_dims,
unsigned rotation)
{
(void)image_dims;
(void)source_dims;
(void)rotation;
return false;
}
static void gl3_xr_forget(gl3_t *gl, unsigned s, bool yield)
{
(void)yield;
gl->xr.shown[s].published = false;
}
static unsigned gl3_xr_eye_screens(const video_frame_info_t *video_info)
{
(void)video_info;
return 0;
}
unsigned gl_openxr_slot_refused(gl_openxr_t *xr, unsigned slot)
{
(void)xr;
(void)slot;
return 0;
}
bool gl_openxr_slot_target(gl_openxr_t *xr, unsigned slot,
unsigned dims, unsigned layers, uint32_t textures[2])
{
(void)xr;
(void)dims;
(void)layers;
textures[0] = RUNTIME_TEX(slot, 0);
textures[1] = RUNTIME_TEX(slot, 1);
return true;
}
void gl_openxr_slot_publish(gl_openxr_t *xr, unsigned slot,
const video_xr_quad_set_t *quads, const void *projection)
{
(void)xr;
(void)quads;
(void)projection;
published[slot]++;
}
void gl_openxr_slot_forget(gl_openxr_t *xr, unsigned slot)
{
(void)xr;
(void)slot;
}
static void gl3_xr_copy_ui(gl3_t *gl, unsigned s, GLuint tex,
unsigned ui_dims)
{
(void)gl;
(void)s;
(void)tex;
(void)ui_dims;
}
static bool gl3_xr_draw_projection(gl3_t *gl,
const video_frame_info_t *video_info)
{
(void)gl;
(void)video_info;
return true;
}
#define OPENXR_PROJECTION_SLOT VIDEO_XR_MAX_SLOTS
#include "driver_code.inc"
#define CHECK(cond) do { if (!(cond)) { \
printf("FAIL line %u: %s\n", (unsigned)__LINE__, #cond); \
failures++; } } while (0)
/* Any chain: the pass steps are stubbed. */
#define FRAME_CHAIN ((gl3_filter_chain_t*)(uintptr_t)1)
/* @screens screens drawn whole, no menu. */
static void plan(unsigned dims, unsigned screens, float x)
{
unsigned s;
memset(&planned, 0, sizeof(planned));
for (s = 0; s < screens; s++)
{
planned.slots[s].dims = dims;
planned.slots[s].layers = 1;
planned.slots[s].view[0] = -1;
planned.quads[s].slot = s;
planned.quads[s].pose.position.x = x;
}
planned.num_quads = screens;
}
static void draw(gl3_t *gl, bool new_frame)
{
video_frame_info_t info;
struct gl3_filter_chain_texture frame;
memset(&info, 0, sizeof(info));
memset(&frame, 0, sizeof(frame));
frame.image = 0x10;
frame.dims = VIDEO_SCALE_PACK(64, 32);
info.frame_repeat = !new_frame;
gl3_xr_draw(gl, &info, new_frame, true, false, NULL, FRAME_CHAIN, NULL,
&frame, frame.dims, 0);
}
static void reset(gl3_t *gl)
{
memset(gl, 0, sizeof(*gl));
offscreen_passes = viewport_passes = replays = 0;
frames_ended = last_pass_dims = 0;
chain_drawn = true;
runtime_attaches = own_attaches = 0;
memset(published, 0, sizeof(published));
}
/* A new frame: each screen straight into the runtime's texture, one
* final pass each, and the chain's frame ended. */
static void test_direct(void)
{
gl3_t gl;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
reset(&gl);
plan(dims, 2, 0.0f);
draw(&gl, true);
CHECK(offscreen_passes == 2 && viewport_passes == 2);
CHECK(frames_ended == 2 && replays == 0);
CHECK(runtime_attaches == 2 && own_attaches == 0);
CHECK(published[0] == 1 && published[1] == 1);
}
/* A repeat of what was shown draws nothing. */
static void test_repeat_same(void)
{
gl3_t gl;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
reset(&gl);
plan(dims, 1, 0.0f);
draw(&gl, true);
viewport_passes = frames_ended = runtime_attaches = 0;
draw(&gl, false);
CHECK(viewport_passes == 0 && replays == 0 && frames_ended == 0);
CHECK(published[0] == 1);
}
/* Resized or moved while paused: the last frame's final pass replayed
* at the new size straight into the runtime's texture. No other pass
* runs and no frame ends, so shader feedback and history stay where
* that frame left them. */
static void test_repeat_changed(void)
{
gl3_t gl;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
unsigned changed = VIDEO_SCALE_PACK(80, 40);
reset(&gl);
plan(dims, 1, 0.0f);
draw(&gl, true);
offscreen_passes = viewport_passes = frames_ended = 0;
runtime_attaches = own_attaches = 0;
plan(changed, 1, 0.0f);
draw(&gl, false);
CHECK(replays == 1 && offscreen_passes == 0 && viewport_passes == 0);
CHECK(frames_ended == 0 && last_pass_dims == changed);
CHECK(runtime_attaches == 1 && own_attaches == 0);
replays = 0;
plan(changed, 1, 0.5f);
draw(&gl, false);
CHECK(replays == 1 && offscreen_passes == 0 && frames_ended == 0);
CHECK(published[0] == 3);
}
/* A chain with no frame drawn since it was built has nothing to replay:
* its passes run, and no frame ends. */
static void test_repeat_unready(void)
{
gl3_t gl;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
unsigned changed = VIDEO_SCALE_PACK(80, 40);
reset(&gl);
plan(dims, 1, 0.0f);
draw(&gl, true);
offscreen_passes = viewport_passes = frames_ended = 0;
chain_drawn = false;
plan(changed, 1, 0.0f);
draw(&gl, false);
CHECK(offscreen_passes == 1 && viewport_passes == 1);
CHECK(replays == 0 && frames_ended == 0);
}
int main(void)
{
test_direct();
test_repeat_same();
test_repeat_changed();
test_repeat_unready();
printf("%u failed (4 GL3 headset draw cases)\n", failures);
return failures ? 1 : 0;
}
@@ -32,6 +32,75 @@ static int failures = 0;
printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
failures++; } } while (0)
static void test_screen_defaults(void)
{
video_screen_xr_t screens[RETRO_VIDEO_VIEWS_MAX + 1];
unsigned i;
memset(screens, 0, sizeof(screens));
screens[RETRO_VIDEO_VIEWS_MAX].scale = 7.0f;
video_views_xr_reset(screens, RETRO_VIDEO_VIEWS_MAX + 1);
for (i = 0; i < RETRO_VIDEO_VIEWS_MAX; i++)
{
CHECK(screens[i].x == 0.0f && screens[i].y == 0.0f
&& screens[i].z == 0.0f);
CHECK(screens[i].yaw == 0.0f && screens[i].pitch == 0.0f
&& screens[i].roll == 0.0f);
CHECK(screens[i].scale == 1.0f && screens[i].visible);
}
CHECK(screens[RETRO_VIDEO_VIEWS_MAX].scale == 7.0f);
screens[1].scale = 3.0f;
video_views_xr_reset(screens, 1);
CHECK(screens[1].scale == 3.0f);
video_views_xr_reset(NULL, RETRO_VIDEO_VIEWS_MAX);
video_views_xr_normalize(NULL);
}
static void test_screen_normalize(void)
{
video_screen_xr_t s;
float invalid[3];
unsigned i;
video_views_xr_reset(&s, 1);
s.x = -10.0f;
s.y = 10.0f;
s.z = 2.5f;
s.yaw = -180.0f;
s.pitch = 180.0f;
s.roll = 45.0f;
s.scale = 0.1f;
s.visible = false;
video_views_xr_normalize(&s);
CHECK(s.x == -10.0f && s.y == 10.0f && s.z == 2.5f);
CHECK(s.yaw == -180.0f && s.pitch == 180.0f && s.roll == 45.0f);
CHECK(s.scale == 0.1f && !s.visible);
s.x = -11.0f;
s.y = 11.0f;
s.z = -11.0f;
s.yaw = -181.0f;
s.pitch = 181.0f;
s.roll = -181.0f;
s.scale = 11.0f;
video_views_xr_normalize(&s);
CHECK(s.x == -10.0f && s.y == 10.0f && s.z == -10.0f);
CHECK(s.yaw == -180.0f && s.pitch == 180.0f && s.roll == -180.0f);
CHECK(s.scale == 10.0f);
s.scale = 0.0f;
video_views_xr_normalize(&s);
CHECK(s.scale == 0.1f);
invalid[0] = (float)HUGE_VAL;
invalid[1] = -(float)HUGE_VAL;
invalid[2] = (float)sqrt(-1.0);
for (i = 0; i < 3; i++)
{
s.x = s.y = s.z = invalid[i];
s.yaw = s.pitch = s.roll = s.scale = invalid[i];
video_views_xr_normalize(&s);
CHECK(s.x == 0.0f && s.y == 0.0f && s.z == 0.0f);
CHECK(s.yaw == 0.0f && s.pitch == 0.0f && s.roll == 0.0f);
CHECK(s.scale == 1.0f && !s.visible);
}
}
static struct retro_video_view mkview(unsigned x, unsigned y,
unsigned w, unsigned h, unsigned screen, unsigned eye)
{
@@ -1315,6 +1384,8 @@ static void test_ui_helpers(void)
int main(void)
{
test_screen_defaults();
test_screen_normalize();
test_validate();
test_frame_helpers();
test_eye_pair();
File diff suppressed because it is too large. Load diff
@@ -288,6 +288,212 @@ static void test_discard_clears(void)
"any other pass's target is not");
}
/* ---- Replay ---- */
MOCK_CREATE(mock_create_image, VkImageCreateInfo, VkImage)
MOCK_DESTROY(mock_destroy_image, VkImage)
MOCK_CREATE(mock_create_image_view, VkImageViewCreateInfo, VkImageView)
MOCK_DESTROY(mock_destroy_image_view, VkImageView)
MOCK_CREATE(mock_create_framebuffer, VkFramebufferCreateInfo, VkFramebuffer)
MOCK_DESTROY(mock_destroy_framebuffer, VkFramebuffer)
MOCK_DESTROY(mock_destroy_render_pass, VkRenderPass)
static void VKAPI_CALL mock_image_requirements(VkDevice d, VkImage i,
VkMemoryRequirements *r)
{
(void)d; (void)i;
r->size = 4096;
r->alignment = 256;
r->memoryTypeBits = 1;
}
static VkResult VKAPI_CALL mock_bind_image_memory(VkDevice d, VkImage i,
VkDeviceMemory m, VkDeviceSize off)
{ (void)d; (void)i; (void)m; (void)off; return VK_SUCCESS; }
/* The view each binding of the final pass's set was last written. */
static VkImageView written_view[4];
static void VKAPI_CALL mock_update_sets(VkDevice d, uint32_t n,
const VkWriteDescriptorSet *w, uint32_t cn, const VkCopyDescriptorSet *c)
{
uint32_t i;
(void)d; (void)cn; (void)c;
for (i = 0; i < n; i++)
if (w[i].dstBinding < 4 && w[i].pImageInfo)
written_view[w[i].dstBinding] = w[i].pImageInfo[0].imageView;
}
/* A replay of the last frame's final pass, in a later frame and at
* another size, reads what that pass read: the second pass's output as
* the frame left it - not the framebuffer the feedback swap gave it -
* that pass's feedback, the history the frame saw, and that frame's
* count; and swaps no feedback. */
static void test_replay(struct vulkan_filter_chain *chain)
{
struct slang_pass *last = chain->passes[PASSES - 1];
struct slang_pass *prev = chain->passes[PASSES - 2];
slang_texture_semantic_meta source_meta[1], feedback_meta[PASSES - 1];
slang_texture_semantic_meta history_meta[2];
slang_texture_semantic_meta unread[SLANG_NUM_TEXTURE_SEMANTICS];
struct slang_framebuffer swapped, feedback;
unsigned i;
VkDescriptorSet sets[INDICES];
uint32_t push[4];
VkViewport vp;
float mvp[16];
vkUpdateDescriptorSets = mock_update_sets;
vkCmdPipelineBarrier = mock_barrier;
vkCmdBindPipeline = (PFN_vkCmdBindPipeline)mock_cmd_nop;
vkCmdBindDescriptorSets= (PFN_vkCmdBindDescriptorSets)mock_cmd_nop;
vkCmdBindVertexBuffers = (PFN_vkCmdBindVertexBuffers)mock_cmd_nop;
vkCmdSetViewport = (PFN_vkCmdSetViewport)mock_cmd_nop;
vkCmdSetScissor = (PFN_vkCmdSetScissor)mock_cmd_nop;
vkCmdDraw = (PFN_vkCmdDraw)mock_cmd_nop;
CHECK(!vulkan_filter_chain_replay_ready(chain),
"a chain with no frame drawn is ready to replay");
memset(unread, 0, sizeof(unread));
for (i = 0; i < SLANG_NUM_TEXTURE_SEMANTICS; i++)
{
last->reflection.semantic_textures[i].data = &unread[i];
last->reflection.semantic_textures[i].size = 1;
}
memset(source_meta, 0, sizeof(source_meta));
memset(feedback_meta, 0, sizeof(feedback_meta));
memset(history_meta, 0, sizeof(history_meta));
source_meta[0].texture = true;
source_meta[0].binding = 0;
feedback_meta[PASSES - 2].texture = true;
feedback_meta[PASSES - 2].binding = 1;
history_meta[1].texture = true;
history_meta[1].binding = 2;
last->reflection.semantic_textures[SLANG_TEXTURE_SEMANTIC_SOURCE].data = source_meta;
last->reflection.semantic_textures[SLANG_TEXTURE_SEMANTIC_SOURCE].size = 1;
last->reflection.semantic_textures[SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK].data = feedback_meta;
last->reflection.semantic_textures[SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK].size = PASSES - 1;
last->reflection.semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].data = history_meta;
last->reflection.semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].size = 2;
last->reflection.semantics[SLANG_SEMANTIC_FRAME_COUNT].push_constant = true;
last->reflection.semantics[SLANG_SEMANTIC_FRAME_COUNT].push_constant_offset = 0;
memset(push, 0, sizeof(push));
memset(sets, 0, sizeof(sets));
last->push.buffer = push;
last->push.stages = 0;
last->sets = sets;
last->final_pass = true;
last->framebuffer = NULL;
/* As the last frame left the chain: the second pass's output was
* frame A, the feedback swap gave it the other framebuffer. */
memset(&swapped, 0, sizeof(swapped));
memset(&feedback, 0, sizeof(feedback));
swapped.view = (VkImageView)(uintptr_t)0xB1;
swapped.size_dims = VIDEO_SCALE_PACK(64, 32);
feedback.view = (VkImageView)(uintptr_t)0xA1;
feedback.size_dims = VIDEO_SCALE_PACK(64, 32);
prev->framebuffer = &swapped;
prev->fb_feedback = &feedback;
texture_array_resize(&chain->common.pass_outputs,
&chain->common.num_pass_outputs, PASSES);
texture_array_resize(&chain->common.fb_feedback,
&chain->common.num_fb_feedback, PASSES - 1);
texture_array_resize(&chain->common.original_history,
&chain->common.num_original_history, 1);
chain->common.pass_outputs[PASSES - 2].texture.view = (VkImageView)(uintptr_t)0xA1;
chain->common.pass_outputs[PASSES - 2].texture.dims = VIDEO_SCALE_PACK(64, 32);
chain->common.fb_feedback[PASSES - 2].texture.view = (VkImageView)(uintptr_t)0xF1;
chain->common.fb_feedback[PASSES - 2].texture.dims = VIDEO_SCALE_PACK(64, 32);
chain->common.original_history[0].texture.view = (VkImageView)(uintptr_t)0xC1;
chain->common.original_history[0].texture.dims = VIDEO_SCALE_PACK(64, 32);
chain->input_texture.view = (VkImageView)(uintptr_t)0xD1;
chain->input_texture.dims = VIDEO_SCALE_PACK(64, 32);
chain->drawn = true;
chain->drawn_frame_count = 7;
last->frame_count = 9;
CHECK(vulkan_filter_chain_replay_ready(chain), "no replay after a frame");
memset(&vp, 0, sizeof(vp));
vp.width = 80.0f;
vp.height = 40.0f;
build_identity_matrix(mvp);
memset(written_view, 0, sizeof(written_view));
vulkan_filter_chain_build_viewport_pass_replay(chain, VK_NULL_HANDLE,
&vp, mvp);
CHECK(written_view[0] == (VkImageView)(uintptr_t)0xA1,
"replay Source: view %p, want the frame's output",
(void*)(uintptr_t)written_view[0]);
CHECK(written_view[1] == (VkImageView)(uintptr_t)0xF1,
"replay PassFeedback: view %p", (void*)(uintptr_t)written_view[1]);
CHECK(written_view[2] == (VkImageView)(uintptr_t)0xC1,
"replay OriginalHistory1: view %p", (void*)(uintptr_t)written_view[2]);
CHECK(push[0] == 7, "replay drew with count %u, want 7", push[0]);
CHECK(last->frame_count == 9, "replay left the count at %u",
(unsigned)last->frame_count);
CHECK(prev->framebuffer == &swapped && prev->fb_feedback == &feedback,
"replay swapped the feedback buffers");
prev->framebuffer = NULL;
prev->fb_feedback = NULL;
memset(&last->reflection, 0, sizeof(last->reflection));
last->push.buffer = NULL;
last->sets = NULL;
}
/* A chain made to replay keeps one history framebuffer more than its
* passes read, so the frame's input goes where the last frame did not
* read: a replay still finds that frame's history. */
static void test_history_spare(struct vulkan_filter_chain *chain)
{
slang_texture_semantic_meta history_meta[3];
struct slang_pass *last = chain->passes[PASSES - 1];
unsigned before;
vkCreateImage = mock_create_image;
vkDestroyImage = mock_destroy_image;
vkCreateImageView = mock_create_image_view;
vkDestroyImageView = mock_destroy_image_view;
vkCreateFramebuffer = mock_create_framebuffer;
vkDestroyFramebuffer = mock_destroy_framebuffer;
vkCreateRenderPass = mock_create_render_pass;
vkDestroyRenderPass = mock_destroy_render_pass;
vkGetImageMemoryRequirements = mock_image_requirements;
vkBindImageMemory = mock_bind_image_memory;
memset(history_meta, 0, sizeof(history_meta));
last->reflection.semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].data = history_meta;
last->reflection.semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].size = 3;
/* Any other chain keeps only what its passes read. */
chain->replay = false;
CHECK(slang_chain_init_history(chain), "history not made");
CHECK(chain->num_history == 2 && chain->common.num_original_history == 2,
"without replay: %u framebuffers for %u read",
(unsigned)chain->num_history,
(unsigned)chain->common.num_original_history);
chain->replay = true;
CHECK(slang_chain_init_history(chain), "history not made");
CHECK(chain->num_history == 3 && chain->common.num_original_history == 2,
"history: %u framebuffers for %u read",
(unsigned)chain->num_history,
(unsigned)chain->common.num_original_history);
if (chain->num_history == 3)
{
chain->history_ring_index = 0;
slang_chain_update_history_info(chain);
before = chain->history_ring_index;
/* What the frame's end writes: the slot before the newest. */
CHECK((before + chain->num_history - 1) % chain->num_history
!= (before + 0) % chain->num_history
&& (before + chain->num_history - 1) % chain->num_history
!= (before + 1) % chain->num_history,
"the frame's end writes a slot the frame read");
CHECK(chain->common.original_history[1].texture.image
== chain->original_history[1]->image,
"history 2 is not the ring's second slot");
}
memset(&last->reflection, 0, sizeof(last->reflection));
}
static bool chain_whole(const struct vulkan_filter_chain *chain)
{
return chain->pass_count == PASSES && chain->num_deferred == INDICES;
@@ -350,6 +556,8 @@ int main(void)
if (chain)
{
test_scanline_band(chain);
test_replay(chain);
test_history_spare(chain);
slang_chain_free(chain);
}
@@ -0,0 +1 @@
.build/
+27
View File
@@ -0,0 +1,27 @@
CC ?= cc
PYTHON ?= python3
BUILD_DIR ?= .build
REPO_ROOT := ../../..
TARGET := $(BUILD_DIR)/vulkan_views_target_test$(EXE)
CFLAGS += -Wall -Wextra -pedantic -std=c89 -Werror=declaration-after-statement \
-Werror=implicit-function-declaration -Wno-long-long \
-I$(REPO_ROOT) -I$(REPO_ROOT)/libretro-common/include \
-I$(REPO_ROOT)/gfx/include -I$(BUILD_DIR)
all: $(TARGET)
$(BUILD_DIR)/driver_code.inc: extract.py $(REPO_ROOT)/gfx/drivers/vulkan.c $(REPO_ROOT)/gfx/video_views_draw.h
@mkdir -p $(BUILD_DIR)
$(PYTHON) extract.py $@
$(TARGET): vulkan_views_target_test.c $(BUILD_DIR)/driver_code.inc
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
check: $(TARGET)
$(RUNNER) $(TARGET)
clean:
rm -f $(BUILD_DIR)/driver_code.inc $(TARGET)
.PHONY: all check clean
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""Exercise Vulkan's shipping target allocation, shared retry policy
and the XR draw: what it draws straight into the runtime's images, and
the slots it cannot draw."""
from pathlib import Path
import re
import sys
root = Path(__file__).resolve().parents[3]
driver = (root / 'gfx/drivers/vulkan.c').read_text()
shared = (root / 'gfx/video_views_draw.h').read_text()
def function(source, name):
masked = re.sub(r'/\*.*?\*/|//[^\n]*|"(?:\\.|[^"\\])*"',
lambda m: ' ' * len(m[0]), source, flags=re.S)
match = re.search(r'static (?:bool|void) ' + re.escape(name)
+ r'\([^;]*?\)\s*\{', masked)
if not match:
raise RuntimeError('function missing: ' + name)
end = masked.index('{', match.start()) + 1
depth = 1
while depth:
depth += (masked[end] == '{') - (masked[end] == '}')
end += 1
return source[match.start():end]
parts = [function(driver, name) for name in
('vulkan_views_retire', 'vulkan_views_make')]
for name in ('VV_FN', 'VV_DRV', 'VV_TARGET', 'VV_TARGET_MAKE',
'VV_TARGET_RETIRE'):
match = re.search(r'^#define ' + name + r'(?:\(|\s).*$', driver, re.M)
if not match:
raise RuntimeError('binding missing: ' + name)
parts.append(match[0])
parts.append(function(shared, 'VV_FN(target_ensure)'))
parts.append(function(driver, 'vulkan_xr_draw_screen'))
parts.append(function(driver, 'vulkan_xr_slot_ensure'))
parts.append(function(driver, 'vulkan_xr_draw'))
Path(sys.argv[1]).write_text('\n\n'.join(parts) + '\n')
@@ -0,0 +1,620 @@
/* Vulkan retained-target failures use the real allocator, retirement and
* shared ensure policy. Only device allocation and logging are replaced.
* The XR draw and its slots run with the runtime's swapchains and the
* pass steps stubbed: what reaches the runtime's images, when a chain
* ends a frame, and when a repeat replays the last frame's final pass. */
#include <stdio.h>
#include <string.h>
#include <vulkan/vulkan.h>
#include "gfx/video_views.h"
#include "gfx/video_defines.h"
#include "gfx/video_xr.h"
#define VULKAN_OPENXR_MAX_IMAGES 4
#define VULKAN_DIRTY_DYNAMIC_BIT 0x0001
#define VULKAN_IMAGE_LAYOUT_TRANSITION(cmd, img, a, b, c, d, e, f) \
do { (void)(cmd); (void)(img); } while (0)
struct vk_image
{
VkImage image;
VkDeviceMemory memory;
VkFramebuffer framebuffer;
};
typedef struct
{
struct { VkPhysicalDeviceLimits limits; } gpu_properties;
VkDevice device;
VkFormat swapchain_format;
struct vulkan_openxr *xr;
} test_context_t;
typedef struct
{
struct vk_image view_retired[2];
} test_chain_t;
typedef struct
{
test_context_t *context;
test_chain_t *chain;
VkCommandBuffer cmd;
struct
{
VkImageView view;
VkSampler sampler;
VkPipeline pipeline;
unsigned dirty;
struct { float data[16]; } mvp;
} tracker;
struct
{
struct vk_image ui;
struct vk_image canvas;
VkFormat ui_format;
VkRenderPass ui_render_pass;
VkRenderPass render_pass;
} views;
struct vk_image cached[3];
unsigned rotation_raw;
struct
{
struct
{
VkImage images[VULKAN_OPENXR_MAX_IMAGES][2];
VkImageView views[VULKAN_OPENXR_MAX_IMAGES][2];
VkFramebuffer framebuffers[VULKAN_OPENXR_MAX_IMAGES][2];
unsigned num_images;
unsigned dims;
unsigned layers;
unsigned failed_dims;
unsigned index[2];
bool drawn;
bool released;
} slots[VIDEO_XR_MAX_SLOTS];
video_xr_quad_set_t set;
unsigned ui_dims;
struct { float data[16]; } mvp;
VkFormat format;
bool mutable_format;
bool proj_shown;
} xr;
} vk_t;
typedef struct vulkan_filter_chain vulkan_filter_chain_t;
typedef struct vulkan_openxr vulkan_openxr_t;
typedef struct
{
video_views_map_t views;
} video_frame_info_t;
static unsigned allocations, destructions, warnings, failures;
static unsigned fail_allocation;
static void vulkan_init_render_target(struct vk_image *img, unsigned dims,
VkFormat format, VkRenderPass pass, test_context_t *context)
{
(void)dims;
(void)format;
(void)pass;
(void)context;
allocations++;
img->image = (VkImage)(uintptr_t)allocations;
img->memory = fail_allocation == 1 ? VK_NULL_HANDLE
: (VkDeviceMemory)(uintptr_t)allocations;
img->framebuffer = fail_allocation == 2 ? VK_NULL_HANDLE
: (VkFramebuffer)(uintptr_t)allocations;
}
static void vulkan_destroy_hdr_buffer(VkDevice device, struct vk_image *img)
{
(void)device;
destructions++;
memset(img, 0, sizeof(*img));
}
static void RARCH_WARN(const char *format, ...)
{
(void)format;
warnings++;
}
static video_xr_quad_set_t planned;
static unsigned acquire_fails;
static void vulkan_xr_plan(vk_t *vk, const video_frame_info_t *video_info,
bool views, bool eyes, unsigned frame_dims, unsigned src_dims,
unsigned ui_dims)
{
(void)video_info;
(void)views;
(void)eyes;
(void)frame_dims;
(void)src_dims;
(void)ui_dims;
vk->xr.set = planned;
}
bool video_xr_shrinks(unsigned image_dims, unsigned source_dims,
unsigned rotation)
{
(void)image_dims;
(void)source_dims;
(void)rotation;
return false;
}
static void vulkan_openxr_slot_forget(vulkan_openxr_t *xr, unsigned slot)
{
(void)xr;
(void)slot;
}
/* What reached the runtime's images: offscreen runs, final passes,
* replays of the last frame's, the size of the last, the render passes
* into a swapchain framebuffer, and frames the chains ended. */
static unsigned offscreen_passes, viewport_passes, replays;
static unsigned last_pass_w, last_pass_h;
static unsigned render_passes, frames_ended;
static bool chain_drawn = true;
static void vulkan_filter_chain_build_offscreen_passes(
vulkan_filter_chain_t *chain, VkCommandBuffer cmd, const VkViewport *vp)
{
(void)chain;
(void)cmd;
(void)vp;
offscreen_passes++;
}
static bool vulkan_filter_chain_replay_ready(
const vulkan_filter_chain_t *chain)
{
(void)chain;
return chain_drawn;
}
static void vulkan_filter_chain_build_viewport_pass_replay(
vulkan_filter_chain_t *chain, VkCommandBuffer cmd,
const VkViewport *vp, const float *mvp)
{
(void)chain;
(void)cmd;
(void)mvp;
replays++;
last_pass_w = (unsigned)vp->width;
last_pass_h = (unsigned)vp->height;
}
static void vulkan_filter_chain_build_viewport_pass(
vulkan_filter_chain_t *chain, VkCommandBuffer cmd,
const VkViewport *vp, const float *mvp)
{
(void)chain;
(void)cmd;
(void)mvp;
viewport_passes++;
last_pass_w = (unsigned)vp->width;
last_pass_h = (unsigned)vp->height;
}
static void vulkan_filter_chain_end_frame(vulkan_filter_chain_t *chain,
VkCommandBuffer cmd)
{
(void)chain;
(void)cmd;
frames_ended++;
}
static void test_begin_render_pass(VkCommandBuffer cmd,
const VkRenderPassBeginInfo *rp, VkSubpassContents contents)
{
(void)cmd;
(void)contents;
if (rp->framebuffer)
render_passes++;
}
static void test_end_render_pass(VkCommandBuffer cmd)
{
(void)cmd;
}
#define vkCmdBeginRenderPass test_begin_render_pass
#define vkCmdEndRenderPass test_end_render_pass
static unsigned slot_creates[VIDEO_XR_MAX_SLOTS];
static unsigned slot_refuses;
static void vulkan_wait_own_submissions(vk_t *vk)
{
(void)vk;
}
static void vulkan_xr_slot_free_views(vk_t *vk, unsigned s)
{
memset(vk->xr.slots[s].views, 0, sizeof(vk->xr.slots[s].views));
memset(vk->xr.slots[s].framebuffers, 0,
sizeof(vk->xr.slots[s].framebuffers));
}
static void vulkan_xr_slot_free(vk_t *vk, unsigned s)
{
memset(&vk->xr.slots[s], 0, sizeof(vk->xr.slots[s]));
}
/* The runtime's swapchains: a refused slot is left with none. */
static bool vulkan_openxr_slot_create(vulkan_openxr_t *xr, unsigned slot,
VkFormat format, bool mutable_format, unsigned dims, unsigned layers,
VkImage (*images)[2], unsigned *num_images)
{
(void)xr;
(void)format;
(void)mutable_format;
(void)dims;
(void)layers;
slot_creates[slot]++;
if (slot_refuses & (1u << slot))
return false;
images[0][0] = (VkImage)(uintptr_t)1;
images[1][0] = (VkImage)(uintptr_t)2;
*num_images = 2;
return true;
}
static VkResult test_create_image_view(VkDevice device,
const VkImageViewCreateInfo *info, const VkAllocationCallbacks *alloc,
VkImageView *view)
{
(void)device;
(void)info;
(void)alloc;
*view = (VkImageView)(uintptr_t)1;
return VK_SUCCESS;
}
static VkResult test_create_framebuffer(VkDevice device,
const VkFramebufferCreateInfo *info, const VkAllocationCallbacks *alloc,
VkFramebuffer *framebuffer)
{
(void)device;
(void)info;
(void)alloc;
*framebuffer = (VkFramebuffer)(uintptr_t)1;
return VK_SUCCESS;
}
#define vkCreateImageView test_create_image_view
#define vkCreateFramebuffer test_create_framebuffer
static bool vulkan_openxr_slot_acquire(vulkan_openxr_t *xr, unsigned slot,
unsigned *index)
{
(void)xr;
(void)index;
return !(acquire_fails & (1u << slot));
}
static void vulkan_xr_copy_ui(vk_t *vk, unsigned s, unsigned ui_dims)
{
(void)vk;
(void)s;
(void)ui_dims;
}
static void vulkan_xr_draw_projection(vk_t *vk,
const video_frame_info_t *video_info)
{
(void)vk;
(void)video_info;
}
#include "driver_code.inc"
#define CHECK(cond) do { if (!(cond)) { \
printf("FAIL line %u: %s\n", (unsigned)__LINE__, #cond); \
failures++; } } while (0)
static void init(vk_t *vk, test_context_t *context, test_chain_t *chain)
{
memset(vk, 0, sizeof(*vk));
memset(context, 0, sizeof(*context));
memset(chain, 0, sizeof(*chain));
context->gpu_properties.limits.maxImageDimension2D = 1024;
context->gpu_properties.limits.maxFramebufferWidth = 1024;
context->gpu_properties.limits.maxFramebufferHeight = 1024;
vk->context = context;
vk->chain = chain;
allocations = destructions = warnings = fail_allocation = 0;
offscreen_passes = viewport_passes = replays = 0;
last_pass_w = last_pass_h = 0;
render_passes = frames_ended = 0;
chain_drawn = true;
memset(slot_creates, 0, sizeof(slot_creates));
slot_refuses = 0;
}
static void test_desktop_failure(bool ui)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
video_views_target_t target;
struct vk_image *image;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
init(&vk, &context, &chain);
memset(&target, 0, sizeof(target));
image = ui ? &vk.views.ui : &vk.views.canvas;
fail_allocation = 1;
CHECK(!vulkan_vv_target_ensure(&vk, &target, image, dims));
CHECK(warnings == 1 && allocations == 1 && destructions == 1);
CHECK(!vulkan_vv_target_ensure(&vk, &target, image, dims));
CHECK(warnings == 1 && allocations == 1);
}
static void test_retirement_saturation(void)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
video_views_target_t targets[3];
unsigned i;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
unsigned changed = VIDEO_SCALE_PACK(80, 32);
init(&vk, &context, &chain);
memset(targets, 0, sizeof(targets));
for (i = 0; i < 3; i++)
CHECK(vulkan_vv_target_ensure(&vk, &targets[i], &vk.cached[i], dims));
CHECK(vulkan_vv_target_ensure(&vk, &targets[0], &vk.cached[0], changed));
CHECK(vulkan_vv_target_ensure(&vk, &targets[1], &vk.cached[1], changed));
CHECK(chain.view_retired[0].image && chain.view_retired[1].image);
CHECK(!vulkan_vv_target_ensure(&vk, &targets[2], &vk.cached[2], changed));
CHECK(targets[2].dims == dims && targets[2].made);
CHECK(allocations == 5 && warnings == 0);
/* Model a completed frame slot freeing a retired image. */
vulkan_destroy_hdr_buffer(context.device, &chain.view_retired[0]);
CHECK(vulkan_vv_target_ensure(&vk, &targets[2], &vk.cached[2], changed));
CHECK(targets[2].dims == changed && targets[2].made);
CHECK(allocations == 6 && warnings == 0);
}
/* @screens screens drawn whole, then the menu. */
static void xr_plan(unsigned dims, unsigned screens)
{
unsigned s;
memset(&planned, 0, sizeof(planned));
for (s = 0; s < screens; s++)
{
planned.slots[s].dims = dims;
planned.slots[s].layers = 1;
planned.slots[s].view[0] = -1;
planned.quads[s].slot = s;
}
planned.slots[VIDEO_XR_MENU_SLOT].dims = dims;
planned.slots[VIDEO_XR_MENU_SLOT].layers = 1;
planned.quads[s].slot = VIDEO_XR_MENU_SLOT;
planned.num_quads = screens + 1;
}
/* Any chain: the pass steps are stubbed. */
#define XR_FRAME_CHAIN ((vulkan_filter_chain_t*)(uintptr_t)1)
static void xr_draw_frame(vk_t *vk, unsigned src_dims, bool new_frame)
{
video_frame_info_t info;
memset(&info, 0, sizeof(info));
vulkan_xr_draw(vk, &info, new_frame, true, false, NULL, XR_FRAME_CHAIN,
NULL, src_dims, src_dims, src_dims, true);
}
static void xr_draw(vk_t *vk, unsigned src_dims)
{
xr_draw_frame(vk, src_dims, true);
}
/* After the frame's submit, as vulkan_xr_release. */
static void xr_release(vk_t *vk)
{
unsigned s;
for (s = 0; s < VIDEO_XR_MAX_SLOTS; s++)
if (vk->xr.slots[s].drawn)
{
vk->xr.slots[s].drawn = false;
vk->xr.slots[s].released = true;
}
}
/* A slot whose image isn't ready keeps its quads: the headset shows its
* last image, or the swapchains a resize keeps until the new ones have
* one. */
static void test_xr_draw_unready(void)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
init(&vk, &context, &chain);
xr_plan(dims, 3);
acquire_fails = (1u << 0) | (1u << VIDEO_XR_MENU_SLOT);
xr_draw(&vk, dims);
CHECK(vk.xr.set.num_quads == 4);
CHECK(!vk.xr.slots[0].drawn && vk.xr.slots[1].drawn);
CHECK(!vk.xr.slots[VIDEO_XR_MENU_SLOT].drawn);
acquire_fails = 0;
xr_draw(&vk, dims);
CHECK(vk.xr.set.num_quads == 4);
CHECK(vk.xr.slots[0].drawn && vk.xr.slots[1].drawn);
}
/* A screen is drawn straight into the runtime's image: one final pass
* per layer, no image of the driver's own between, and no allocation.
* A new frame ends the chain's frame; a repeat whose image the runtime
* has draws nothing. */
static void test_xr_draw_direct(void)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
init(&vk, &context, &chain);
acquire_fails = 0;
xr_plan(dims, 2);
xr_draw(&vk, dims);
CHECK(vk.xr.slots[0].drawn && vk.xr.slots[1].drawn);
CHECK(offscreen_passes == 2 && viewport_passes == 2);
CHECK(render_passes == 2 && frames_ended == 2 && replays == 0);
CHECK(allocations == 0);
xr_release(&vk);
xr_draw_frame(&vk, dims, false);
CHECK(!vk.xr.slots[0].drawn && !vk.xr.slots[1].drawn);
CHECK(viewport_passes == 2 && frames_ended == 2);
}
/* A new frame with no frame data - a movie frame the driver imported -
* reaches the headset, as a core frame does. */
static void test_xr_draw_movie_frame(void)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
init(&vk, &context, &chain);
acquire_fails = 0;
xr_plan(dims, 1);
xr_draw(&vk, dims);
xr_release(&vk);
viewport_passes = frames_ended = 0;
xr_draw_frame(&vk, dims, true);
CHECK(vk.xr.slots[0].drawn);
CHECK(viewport_passes == 1 && frames_ended == 1 && replays == 0);
}
/* Resized while paused: the last frame's final pass is replayed at the
* new size straight into the new image. No other pass runs and no frame
* ends, so shader feedback and history stay where that frame left them. */
static void test_xr_draw_resize_repeat(void)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
unsigned changed = VIDEO_SCALE_PACK(80, 40);
init(&vk, &context, &chain);
acquire_fails = 0;
xr_plan(dims, 1);
xr_draw(&vk, dims);
xr_release(&vk);
offscreen_passes = viewport_passes = render_passes = frames_ended = 0;
xr_plan(changed, 1);
xr_draw_frame(&vk, dims, false);
CHECK(vk.xr.slots[0].drawn && vk.xr.slots[0].dims == changed);
CHECK(replays == 1 && render_passes == 1);
CHECK(offscreen_passes == 0 && viewport_passes == 0);
CHECK(frames_ended == 0 && allocations == 0);
CHECK(last_pass_w == 80 && last_pass_h == 40);
CHECK(slot_creates[0] == 2);
}
/* A chain with no frame drawn since it was built - a preset loaded while
* paused - has nothing to replay: its passes run, and no frame ends. */
static void test_xr_draw_repeat_unready(void)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
unsigned changed = VIDEO_SCALE_PACK(80, 40);
init(&vk, &context, &chain);
acquire_fails = 0;
xr_plan(dims, 1);
xr_draw(&vk, dims);
xr_release(&vk);
offscreen_passes = viewport_passes = frames_ended = 0;
chain_drawn = false;
xr_plan(changed, 1);
xr_draw_frame(&vk, dims, false);
CHECK(offscreen_passes == 1 && viewport_passes == 1);
CHECK(replays == 0 && frames_ended == 0);
}
/* Model completed frame slots freeing the retire ring. */
static void xr_retired(vk_t *vk)
{
unsigned i;
for (i = 0; i < 2; i++)
if (vk->chain->view_retired[i].image)
vulkan_destroy_hdr_buffer(vk->context->device,
&vk->chain->view_retired[i]);
}
/* A size the runtime refused is not tried again. A resize back to it
* keeps the quad: the slot still has the swapchain it had. */
static void test_xr_draw_slot_refused(void)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
unsigned changed = VIDEO_SCALE_PACK(80, 32);
init(&vk, &context, &chain);
acquire_fails = 0;
xr_plan(dims, 1);
xr_draw(&vk, dims);
CHECK(vk.xr.set.num_quads == 2 && vk.xr.slots[0].drawn);
slot_refuses = 1u << 0;
xr_plan(changed, 1);
xr_draw(&vk, changed);
xr_retired(&vk);
CHECK(vk.xr.set.num_quads == 1);
CHECK(vk.xr.set.quads[0].slot == VIDEO_XR_MENU_SLOT);
CHECK(slot_creates[0] == 2);
slot_refuses = 0;
xr_plan(dims, 1);
xr_draw(&vk, dims);
xr_retired(&vk);
CHECK(vk.xr.set.num_quads == 2 && vk.xr.slots[0].drawn);
CHECK(slot_creates[0] == 3);
xr_plan(changed, 1);
xr_draw(&vk, changed);
xr_retired(&vk);
CHECK(vk.xr.set.num_quads == 2 && !vk.xr.slots[0].drawn);
CHECK(vk.xr.slots[0].dims == dims && slot_creates[0] == 3);
}
/* A slot the runtime never made is left out, and not tried again every
* frame. */
static void test_xr_draw_slot_unmade(void)
{
vk_t vk;
test_context_t context;
test_chain_t chain;
unsigned dims = VIDEO_SCALE_PACK(64, 32);
init(&vk, &context, &chain);
acquire_fails = 0;
slot_refuses = 1u << 0;
xr_plan(dims, 1);
xr_draw(&vk, dims);
CHECK(vk.xr.set.num_quads == 1);
CHECK(vk.xr.set.quads[0].slot == VIDEO_XR_MENU_SLOT);
xr_draw(&vk, dims);
CHECK(vk.xr.set.num_quads == 1 && slot_creates[0] == 1);
}
int main(void)
{
test_desktop_failure(false);
test_desktop_failure(true);
test_retirement_saturation();
test_xr_draw_unready();
test_xr_draw_direct();
test_xr_draw_movie_frame();
test_xr_draw_resize_repeat();
test_xr_draw_repeat_unready();
test_xr_draw_slot_refused();
test_xr_draw_slot_unmade();
printf("%u failed (10 Vulkan target cases)\n", failures);
return failures ? 1 : 0;
}
@@ -45,10 +45,14 @@ bool openxr_session_ended(openxr_session_t *session)
{ (void)session; return false; }
XrSwapchain openxr_frame_cursor(const openxr_frame_t *frame)
{ (void)frame; return test_frame ? (XrSwapchain)(uintptr_t)1 : XR_NULL_HANDLE; }
unsigned openxr_frame_cursor_w(const openxr_frame_t *frame)
{ (void)frame; return VIDEO_XR_CURSOR_W; }
void openxr_frame_set_input_hooks(const openxr_input_hooks_t *hooks)
{ (void)hooks; }
XrTime openxr_frame_predicted_time(openxr_frame_t *frame)
{ (void)frame; return 0; }
XrDuration openxr_frame_period(openxr_frame_t *frame)
{ (void)frame; return 0; }
void openxr_frame_get_shown_quads(openxr_frame_t *frame,
video_xr_quad_set_t *out, XrSpace *space)
{ (void)frame; memset(out, 0, sizeof(*out)); *space = XR_NULL_HANDLE; }
@@ -57,6 +61,17 @@ bool video_driver_get_views_core(const video_views_map_t **map, unsigned *dims)
{ (void)map; (void)dims; return false; }
bool video_driver_get_viewport_info(struct video_viewport *vp)
{ (void)vp; return false; }
void input_driver_platform_request(enum input_platform_request req)
{ (void)req; }
void runloop_msg_queue_push(const char *msg, size_t len, unsigned prio,
unsigned duration, bool flush, char *title,
enum message_queue_icon icon, enum message_queue_category category)
{ (void)msg; (void)len; (void)prio; (void)duration; (void)flush;
(void)title; (void)icon; (void)category; }
const char *msg_hash_to_str(enum msg_hash_enums msg)
{ (void)msg; return ""; }
void video_driver_headset_edit(bool editing, unsigned seq)
{ (void)editing; (void)seq; }
bool input_autoconfigure_connect_ex(const char *name,
const char *display_name, const char *phys, const char *driver,
unsigned port, unsigned vid, unsigned pid, uint8_t flags)
@@ -100,9 +115,208 @@ int video_xr_pick(const video_xr_quad_set_t *set, unsigned laser, bool menu,
picked_laser = laser; picked_menu = menu; picks++;
return -1;
}
int video_xr_pick_edit(const video_xr_quad_set_t *set, bool stuck,
const video_xr_vec3_t *o, const video_xr_vec3_t *d,
float *u, float *v, float *t, bool *stick)
{
(void)set; (void)stuck; (void)o; (void)d; (void)u; (void)v; (void)t;
if (stick)
*stick = false;
picked_laser = VIDEO_XR_LIVE_EDIT; picked_menu = false; picks++;
return -1;
}
float video_xr_cursor(const video_xr_quad_t *q, float u, float v,
float t, video_xr_pose_t *pose)
{ (void)q; (void)u; (void)v; (void)t; (void)pose; return 0.01f; }
void video_xr_beam(const video_xr_vec3_t *o, const video_xr_vec3_t *d,
float len, const video_xr_vec3_t *eye, video_xr_pose_t *pose)
{ (void)o; (void)d; (void)len; (void)eye; memset(pose, 0, sizeof(*pose)); }
void video_xr_tag(const video_xr_vec3_t *hand, const video_xr_vec3_t *eye,
video_xr_pose_t *pose)
{ (void)hand; (void)eye; memset(pose, 0, sizeof(*pose)); }
/* What edit mode reaches, never run here. */
void openxr_frame_get_quads(openxr_frame_t *f, video_xr_quad_set_t *out)
{ (void)f; memset(out, 0, sizeof(*out)); }
void openxr_frame_get_screens(openxr_frame_t *f, video_xr_pose_t *pose,
XrSpace *space)
{ (void)f; memset(pose, 0, sizeof(*pose)); *space = XR_NULL_HANDLE; }
void config_get_video_screen_xr(unsigned screen, video_screen_xr_t *out)
{ (void)screen; memset(out, 0, sizeof(*out)); }
bool config_get_video_menu_xr(video_screen_xr_t *out)
{ memset(out, 0, sizeof(*out)); return false; }
void config_set_video_screen_xr(unsigned screen,
const video_screen_xr_t *values)
{ (void)screen; (void)values; }
void config_set_video_menu_xr(const video_screen_xr_t *values)
{ (void)values; }
void video_views_xr_reset(video_screen_xr_t *screens, unsigned count)
{ memset(screens, 0, count * sizeof(*screens)); }
void video_views_xr_normalize(video_screen_xr_t *screen) { (void)screen; }
void video_xr_compose(const video_xr_pose_t *a, const video_xr_pose_t *b,
video_xr_pose_t *out)
{ (void)a; *out = *b; }
void video_xr_pose_invert(const video_xr_pose_t *a, video_xr_pose_t *out)
{ *out = *a; }
void video_xr_grab_hold(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_quat_t *q)
{ (void)aim; g->turn = *q; }
bool video_xr_grab_pose(const video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_quad_set_t *targets, unsigned skip,
const video_xr_pose_t *start, video_xr_pose_t *pose,
video_screen_xr_t *out, unsigned *snapped)
{
(void)g; (void)targets; (void)skip; (void)start; (void)out;
if (snapped)
*snapped = 0;
*pose = *aim;
return false;
}
void video_xr_grab_stick(video_xr_grab_t *g, float x, float y, double dt,
float *scale)
{ (void)g; (void)x; (void)y; (void)dt; (void)scale; }
unsigned video_xr_grab_off_step(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_vec3_t *head, float step,
const float *rest)
{ (void)g; (void)aim; (void)head; (void)step; (void)rest; return 0; }
unsigned video_xr_grab_step(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_vec3_t *head, float step, unsigned axes, float *rest,
const video_xr_quad_set_t *targets, unsigned skip,
const video_xr_pose_t *start, const video_xr_hinge_t *lid)
{
(void)g; (void)aim; (void)head; (void)step; (void)axes; (void)rest;
(void)targets; (void)skip; (void)start; (void)lid;
return 0;
}
unsigned video_xr_grab_turn_lid(video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_hinge_t *lid, float x,
float y, double dt)
{ (void)g; (void)aim; (void)lid; (void)x; (void)y; (void)dt; return 0; }
bool video_xr_grab_near_hinge(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
const video_xr_hinge_t *h, float reach)
{
(void)g; (void)aim; (void)targets; (void)h; (void)reach;
return false;
}
void video_xr_rest_take(const video_xr_quad_set_t *set, unsigned slot,
float width, float height, float gap, const video_xr_hinge_t *h,
video_xr_pose_t *pose)
{
(void)set; (void)slot; (void)width; (void)height; (void)gap; (void)h;
(void)pose;
}
bool video_xr_grab_through(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
const video_xr_hinge_t *h)
{ (void)g; (void)aim; (void)targets; (void)h; return false; }
unsigned video_xr_grab_hinge(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
unsigned skip, const video_xr_pose_t *start, video_xr_pose_t *pose,
video_xr_hinge_t *hinge)
{
(void)g; (void)targets; (void)skip; (void)start; (void)hinge;
*pose = *aim;
return 0;
}
void video_xr_grab_put(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_pose_t *pose)
{ (void)g; (void)aim; (void)pose; }
void video_xr_grab_put_hinge(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_hinge_t *h, const video_xr_pose_t *pose, bool turned)
{ (void)g; (void)aim; (void)h; (void)pose; (void)turned; }
void video_xr_grab_put_line(video_xr_grab_t *g, const video_xr_pose_t *aim,
const video_xr_hinge_t *h)
{ (void)g; (void)aim; (void)h; }
unsigned video_xr_grab_turn_hinge(video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_hinge_t *h, float x,
float y, double dt)
{ (void)g; (void)aim; (void)h; (void)x; (void)y; (void)dt; return 0; }
unsigned video_xr_grab_step_hinge(video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_hinge_t *h, float size,
unsigned axes, float *rest, const video_xr_quad_set_t *targets,
unsigned skip, const video_xr_pose_t *start)
{
(void)g; (void)aim; (void)h; (void)size; (void)axes; (void)rest;
(void)targets; (void)skip; (void)start;
return 0;
}
unsigned video_xr_grab_off_hinge(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_hinge_t *h, float size,
const float *rest)
{ (void)g; (void)aim; (void)h; (void)size; (void)rest; return 0; }
void video_xr_turn_back(video_xr_pose_t *pose) { (void)pose; }
void video_xr_menu_handle(const video_xr_quad_t *menu, video_xr_quad_t *out)
{ *out = *menu; }
void video_xr_outline(const video_xr_quad_t *q, video_xr_quad_t *out)
{ *out = *q; }
void video_xr_outline_crop(video_xr_quad_t *o, unsigned hide, bool back,
unsigned *x, unsigned *w)
{ (void)o; (void)hide; (void)back; *x = 0; *w = VIDEO_XR_OUTLINE_DIM; }
unsigned video_xr_outline_cell(unsigned hide, bool back, unsigned lit)
{ (void)hide; (void)back; return VIDEO_XR_OUTLINE_X(lit); }
bool video_xr_hinge_line(const video_xr_hinge_t *h, float width,
float height, const video_xr_quad_t *target, video_xr_vec3_t *a,
video_xr_vec3_t *b)
{
(void)h; (void)width; (void)height; (void)target; (void)a; (void)b;
return false;
}
void video_xr_hinge_place(video_xr_hinge_t *h, const video_xr_pose_t *pose,
float width, float height, const video_xr_quad_t *target, bool over)
{
(void)h; (void)pose; (void)width; (void)height; (void)target;
(void)over;
}
unsigned video_xr_rest_hinges(const video_xr_quad_set_t *set,
unsigned skip, float gap, video_xr_rest_t *rest, unsigned max)
{
(void)set; (void)skip; (void)gap; (void)rest; (void)max;
return 0;
}
bool video_xr_rest_hinge(const video_xr_quad_set_t *set, unsigned slot,
unsigned skip, float width, float height, float gap,
const video_xr_pose_t *pose, video_xr_hinge_t *hinge)
{
(void)set; (void)slot; (void)skip; (void)width; (void)height;
(void)gap; (void)pose; (void)hinge;
return false;
}
bool video_xr_hinge_settle(const video_xr_quad_set_t *set, unsigned slot,
unsigned skip, float width, float height, float gap,
const video_xr_hinge_t *h, const video_xr_pose_t *hand,
video_xr_pose_t *pose)
{
(void)set; (void)slot; (void)skip; (void)width; (void)height;
(void)gap; (void)h; (void)hand; (void)pose;
return false;
}
bool video_xr_grab_settle(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
unsigned skip, const video_xr_hinge_t *h, video_xr_pose_t *pose)
{
(void)g; (void)aim; (void)targets; (void)skip; (void)h; (void)pose;
return false;
}
bool video_xr_grab_kept(const video_xr_grab_t *g,
const video_xr_pose_t *aim, const video_xr_quad_set_t *targets,
const video_xr_pose_t *start, video_xr_hinge_t *kept)
{
(void)g; (void)aim; (void)targets; (void)start; (void)kept;
return false;
}
void openxr_frame_publish_grabs(openxr_frame_t *f, const openxr_grabs_t *g)
{ (void)f; (void)g; }
void video_xr_smooth_step(video_xr_smooth_t *s, unsigned level,
const video_xr_pose_t *raw, double dt, video_xr_pose_t *out)
{ (void)s; (void)level; (void)dt; *out = *raw; }
void openxr_frame_publish_aims(openxr_frame_t *f, const openxr_aims_t *a)
{ (void)f; (void)a; }
void openxr_frame_get_aims(openxr_frame_t *f, openxr_aims_t *out)
{ (void)f; memset(out, 0, sizeof(*out)); }
int openxr_frame_recenters(openxr_frame_t *f)
{ (void)f; return 0; }
static video_xr_quad_set_t test_shown;
static unsigned test_under;
static XrResult XRAPI_CALL locate(XrSpace space, XrSpace base,
XrTime time, XrSpaceLocation *out)
@@ -205,7 +419,8 @@ static void publish_pointer(unsigned laser, bool menu)
openxr_frame_t *frame = input_openxr_st.frame;
test_menu_open = menu;
input_openxr_st.frame = NULL;
input_openxr_poll(4, VIDEO_OPENXR_CONTROLLERS_COMBINED, laser, 0.5f);
input_openxr_poll(4, VIDEO_OPENXR_CONTROLLERS_COMBINED, laser,
VIDEO_OPENXR_LASER_BEAM_EDIT, 0.5f);
input_openxr_st.frame = frame;
}
static void replace_pointer(void)
@@ -224,29 +439,40 @@ static void pointer_tests(void)
{
publish_pointer(laser, menu != 0);
picks = 0;
input_openxr_frame_layers(NULL, 1, layers, 2);
input_openxr_frame_layers(NULL, 1, &test_shown, XR_NULL_HANDLE,
layers, 2, &test_under);
if (laser == VIDEO_OPENXR_LASER_OFF)
CHECK(picks == 0, "disabled pointer picked a quad");
else
CHECK(picks == 2 && picked_laser == laser
&& picked_menu == (menu != 0), "pointer pair changed");
}
publish_pointer(VIDEO_OPENXR_LASER_LAST, false);
picks = 0;
input_openxr_frame_layers(NULL, 1, &test_shown, XR_NULL_HANDLE,
layers, 2, &test_under);
CHECK(picks == 2 && picked_laser == VIDEO_OPENXR_LASER_AUTO
&& !picked_menu, "a laser past the choices was not Auto");
publish_pointer(VIDEO_OPENXR_LASER_AUTO, false);
after_load = replace_pointer;
picks = 0;
input_openxr_frame_layers(NULL, 1, layers, 2);
input_openxr_frame_layers(NULL, 1, &test_shown, XR_NULL_HANDLE,
layers, 2, &test_under);
CHECK(picks == 2 && picked_laser == VIDEO_OPENXR_LASER_AUTO
&& !picked_menu, "frame mixed two published pointer configurations");
input_openxr_frame_layers(NULL, 1, layers, 2);
input_openxr_frame_layers(NULL, 1, &test_shown, XR_NULL_HANDLE,
layers, 2, &test_under);
CHECK(picked_laser == VIDEO_OPENXR_LASER_ALWAYS && picked_menu,
"next frame did not observe replacement pointer pair");
input_openxr_st.yield = true;
publish_pointer(VIDEO_OPENXR_LASER_ALWAYS, true);
picks = 0;
input_openxr_frame_layers(NULL, 1, layers, 2);
input_openxr_frame_layers(NULL, 1, &test_shown, XR_NULL_HANDLE,
layers, 2, &test_under);
CHECK(picks == 0, "yielding pointer remained visible");
publish_pointer(VIDEO_OPENXR_LASER_ALWAYS, false);
input_openxr_frame_layers(NULL, 1, layers, 2);
input_openxr_frame_layers(NULL, 1, &test_shown, XR_NULL_HANDLE,
layers, 2, &test_under);
CHECK(picks == 2 && !picked_menu, "closed menu did not end pointer yield");
input_openxr_st.SyncActions = sync_actions;
input_openxr_st.GetActionStateBoolean = read_bool;
@@ -254,14 +480,16 @@ static void pointer_tests(void)
input_openxr_st.GetActionStateVector2f = read_vector;
test_navigation = test_menu_open = true;
input_openxr_poll(4, VIDEO_OPENXR_CONTROLLERS_COMBINED,
VIDEO_OPENXR_LASER_ALWAYS, 0.5f);
VIDEO_OPENXR_LASER_ALWAYS, VIDEO_OPENXR_LASER_BEAM_EDIT, 0.5f);
picks = 0;
input_openxr_frame_layers(NULL, 1, layers, 2);
input_openxr_frame_layers(NULL, 1, &test_shown, XR_NULL_HANDLE,
layers, 2, &test_under);
CHECK(picks == 0, "post-navigation publication did not hide pointer");
test_navigation = test_menu_open = false;
input_openxr_poll(4, VIDEO_OPENXR_CONTROLLERS_COMBINED,
VIDEO_OPENXR_LASER_ALWAYS, 0.5f);
input_openxr_frame_layers(NULL, 1, layers, 2);
VIDEO_OPENXR_LASER_ALWAYS, VIDEO_OPENXR_LASER_BEAM_EDIT, 0.5f);
input_openxr_frame_layers(NULL, 1, &test_shown, XR_NULL_HANDLE,
layers, 2, &test_under);
CHECK(picks == 2 && picked_laser == VIDEO_OPENXR_LASER_ALWAYS
&& !picked_menu, "completed poll published the wrong pointer pair");
}
@@ -340,7 +568,8 @@ int main(void)
service(false, false);
CHECK(input_openxr_st.haptic[0] == 0.0f, "unfocused result cached as applied");
test_focused = false;
input_openxr_poll(4, VIDEO_OPENXR_CONTROLLERS_COMBINED, VIDEO_OPENXR_LASER_OFF, 0.5f);
input_openxr_poll(4, VIDEO_OPENXR_CONTROLLERS_COMBINED, VIDEO_OPENXR_LASER_OFF,
VIDEO_OPENXR_LASER_BEAM_EDIT, 0.5f);
test_focused = true;
apply_result[0] = XR_SUCCESS;
service(false, false);
File diff suppressed because it is too large. Load diff
@@ -19,7 +19,7 @@
* playlist_nav pattern). A headset gives each eye its own image, so
* the list leaves Stereo Mode out while Headset Output is on, and
* pressing Headset Output rebuilds the open list with the cursor still
* on it. Headset Refresh Rate sits under Headset Output, and with no
* on it. Headset Refresh Rate sits below Headset Output, and with no
* headset lists the fixed rates.
*
* The video driver stays null: the list reads only the driver's name,
@@ -219,11 +219,12 @@ static void lane_headset_rows(void)
static const char *const rows[] = {
MENU_ENUM_LABEL_VIDEO_OPENXR_ENABLE_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_REFRESH_RATE_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_DISTANCE_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_WIDTH_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_PLACEMENT_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_CONTROLLERS_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_STR };
MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_LASER_BEAM_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_PLACEMENT_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_DISTANCE_STR,
MENU_ENUM_LABEL_VIDEO_OPENXR_WIDTH_STR };
unsigned i, d;
#ifdef HAVE_VULKAN
@@ -340,9 +341,9 @@ static void lane_toggle(void)
"the cursor left Headset Output when Start turned it off");
}
/* Right under Headset Output. With no headset to list its rates it
* steps through Auto, Headset's Choice and 72, 90, 120 and 144 Hz,
* round, and its dropdown lists the same. */
/* Right after the edit rows Headset Output shows. With no headset to
* list its rates it steps through Auto, Headset's Choice and 72, 90,
* 120 and 144 Hz, round, and its dropdown lists the same. */
static void lane_refresh_rate(void)
{
static const unsigned want[] = { 0, 1, 72, 90, 120, 144, 0 };
@@ -350,7 +351,7 @@ static void lane_refresh_rate(void)
"72 Hz", "90 Hz", "120 Hz", "144 Hz", "Auto" };
char s[64];
unsigned i;
int enable, rate;
int edit, rate;
file_list_t *buf;
settings_t *settings = config_get_ptr();
rarch_setting_t *setting = menu_setting_find_enum(
@@ -359,11 +360,11 @@ static void lane_refresh_rate(void)
set_headset(true);
set_driver(HEADSET_DRIVER);
open_stereo_list();
enable = row(MENU_ENUM_LABEL_VIDEO_OPENXR_ENABLE_STR);
edit = row(msg_hash_to_str(MENU_ENUM_LABEL_VIDEO_OPENXR_EDIT_PAUSE));
rate = row(MENU_ENUM_LABEL_VIDEO_OPENXR_REFRESH_RATE_STR);
CHECK(rate >= 0 && rate == enable + 1,
"Headset Refresh Rate is not right under Headset Output "
"(rows %d and %d)", enable, rate);
CHECK(rate >= 0 && rate == edit + 1,
"Headset Refresh Rate is not right under While Editing Screens "
"(rows %d and %d)", edit, rate);
CHECK(setting != NULL, "fixture: no Headset Refresh Rate setting");
if (!setting)
return;
@@ -22,8 +22,10 @@ static runloop_state_t runloop_st;
static bool hardcore;
bool rcheevos_hardcore_active(void) { return hardcore; }
bool rcheevos_is_pause_allowed(void) { return !hardcore; }
settings_t *config_get_ptr(void) { return &settings; }
runloop_state_t *runloop_state_get_ptr(void) { return &runloop_st; }
uint32_t runloop_get_flags(void) { return runloop_st.flags; }
void RARCH_LOG(const char *fmt, ...) { (void)fmt; }
bool command_event(enum event_command cmd, void *data)
{ (void)cmd; (void)data; return false; }
File diff suppressed because it is too large. Load diff
+16
View File
@@ -0,0 +1,16 @@
#version 450
layout(std140, set = 0, binding = 0) uniform UBO { mat4 MVP; uint FrameCount; } global;
#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
void main() { gl_Position = global.MVP * Position; vTexCoord = TexCoord; }
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D PassFeedback0;
void main()
{
FragColor = vec4(fract(texture(PassFeedback0, vTexCoord).r + 0.03125),
float(global.FrameCount % 251u) / 250.0, 0.25, 1.0);
}
+5
View File
@@ -0,0 +1,5 @@
shaders = 2
shader0 = feedback.slang
feedback_pass = 0
float_framebuffer0 = true
shader1 = feedback.slang
+3679 -22
View File
File diff suppressed because it is too large. Load diff
+49 -1
View File
@@ -22,6 +22,18 @@ class GlLanes(unittest.TestCase):
[{k: v for k, v in c.items() if k != 'check'} for c in run.CASES])
self.gl_cases = run.gl_cases()
def test_custom_selection(self):
names = ['custom-' + k for k in ('offset', 'rotation', 'hidden',
'recenter', 'menu', 'tracked', 'repeat')]
selected, error = run.select(names, False, False)
self.assertIsNone(error)
self.assertEqual([c['name'] for c in selected], names)
for lane, driver, context in run.GL_LANES:
wanted = [lane + '-' + n for n in names]
selected, error = run.select(wanted, True, False)
self.assertIsNone(error)
self.assertEqual([c['name'] for c in selected], wanted)
def test_names(self):
names = [c['name'] for c in self.gl_cases]
self.assertEqual(len(set(names)), len(names))
@@ -107,7 +119,43 @@ class GlLanes(unittest.TestCase):
def test_extra_cases_gl_only(self):
names = {c['name'] for c in run.CASES}
self.assertTrue(all(c['name'] not in names for c in run.GL_EXTRA))
self.assertEqual(len(run.CASES), 141)
self.assertEqual(len(run.CASES), 213)
def test_edit_lanes(self):
names = [c['name'] for c in self.gl_cases]
for n in ('edit-grab', 'edit-reset', 'edit-menu-front',
'edit-follow', 'edit-no-map', 'edit-fallback'):
self.assertIn('glcore-egl-' + n, names)
for n in ('edit-grab', 'edit-reset', 'edit-outlines'):
self.assertIn('gl-egl-' + n, names)
self.assertIn('glcore-egl-edit-beam', names)
self.assertIn('gl-egl-edit-beam', names)
self.assertNotIn('glcore-glx-edit-beam', names)
self.assertIn('laser-beam-always', [c['name'] for c in run.CASES])
self.assertIn('gl-glx-edit-grab', names)
self.assertIn('glcore-glx-edit-command', names)
self.assertNotIn('glcore-glx-edit-reset', names)
self.assertNotIn('gl-egl-edit-menu-front', names)
self.assertNotIn('gl-egl-edit-menu-size', names)
self.assertNotIn('glcore-egl-edit-tilt', names)
self.assertIn('glcore-egl-edit-choose', names)
self.assertIn('gl-egl-edit-choose', names)
self.assertNotIn('glcore-glx-edit-choose', names)
self.assertNotIn('glcore-egl-edit-handle-first', names)
self.assertIn('glcore-egl-edit-snap', names)
self.assertIn('gl-egl-edit-snap', names)
self.assertIn('glcore-egl-edit-tag', names)
self.assertIn('gl-egl-edit-tag', names)
self.assertNotIn('glcore-glx-edit-tag', names)
self.assertIn('glcore-egl-edit-tag-narrow', names)
self.assertIn('gl-egl-edit-tag-narrow', names)
self.assertIn('glcore-egl-edit-back', names)
self.assertIn('gl-egl-edit-back', names)
selected, error = run.select(['edit-grab', 'edit-pause'], False,
False)
self.assertIsNone(error)
self.assertEqual([c['name'] for c in selected],
['edit-grab', 'edit-pause'])
class Select(unittest.TestCase):
+61 -15
View File
@@ -37,6 +37,8 @@
* RA_XR_LAYER_QUAD_INDEX0 1: quads show array layer 0 whatever their
* imageArrayIndex, as SteamVR on the Steam
* Frame does
* RA_XR_LAYER_MAX_WIDTH the runtime's maxSwapchainImageWidth, at most
* this; 0 or unset leaves it
*
* Script lines:
* head <x> <y> <z> <yaw> the VIEW space's pose in LOCAL, yaw in degrees
@@ -76,9 +78,11 @@
* action <set>/<name>[@left|@right] <x> [<y>]
* an action's state while its set is synced:
* boolean x != 0, float x, vector2f x y
* aim <left|right> <px> <py> <pz> <tx> <ty> <tz>
* aim <left|right> <px> <py> <pz> <tx> <ty> <tz> [<roll>]
* that hand's pose spaces at p, -Z towards t,
* in LOCAL; in VIEW through the scripted head
* rolled roll degrees about the aim's own Z (+X
* towards +Y), in LOCAL; in VIEW through the
* scripted head
* aim <left|right> off that hand untracked
*
* A session on an OpenGL binding (XR_MNDX_egl_enable's EGL or
@@ -93,7 +97,9 @@
* a later line of the same kind replaces them (head off, fail off). state
* and space fire once per change of the file. Every read starts from no
* actions, no aims and valid views: a script is the whole controller
* state. Action states and hand poses never come from the runtime.
* state. Action states and hand poses never come from the runtime. A
* synced set's actions all read bound; those the script leaves out, at
* rest.
*/
#include <math.h>
@@ -177,6 +183,7 @@ static struct
char dir[1024];
unsigned snap_every;
bool quad_index0;
uint32_t max_width;
char script[1024];
struct timespec script_mtime;
ino_t script_ino;
@@ -258,6 +265,7 @@ static struct
PFN_xrBeginFrame BeginFrame;
PFN_xrEndFrame EndFrame;
PFN_xrEnumerateViewConfigurationViews EnumerateViewConfigurationViews;
PFN_xrGetSystemProperties GetSystemProperties;
XrInstance instance;
bool frame; /* the application enabled FRAME_EXT */
@@ -315,6 +323,7 @@ static void layer_open(void)
const char *snap = getenv("RA_XR_LAYER_SNAP_EVERY");
const char *scr = getenv("RA_XR_LAYER_SCRIPT");
const char *idx0 = getenv("RA_XR_LAYER_QUAD_INDEX0");
const char *maxw = getenv("RA_XR_LAYER_MAX_WIDTH");
char path[1100];
if (dir && *dir && !L.out)
{
@@ -324,6 +333,7 @@ static void layer_open(void)
}
L.snap_every = snap ? (unsigned)strtoul(snap, NULL, 10) : 0;
L.quad_index0 = idx0 && atoi(idx0) != 0;
L.max_width = maxw ? (uint32_t)strtoul(maxw, NULL, 10) : 0;
if (scr)
snprintf(L.script, sizeof(L.script), "%s", scr);
}
@@ -395,8 +405,16 @@ static bool set_synced(XrActionSet s)
return false;
}
/* Every action of a synced set is bound, as a runtime binds what each
* profile was suggested. Caller holds L.lock. */
static bool action_bound(XrAction action)
{
const struct action_rec *a = find_action(action);
return a && set_synced(a->set);
}
/* The script's value for an action asked for a hand, while its set is
* synced; NULL reads inactive. Caller holds L.lock. */
* synced; NULL reads at rest. Caller holds L.lock. */
static const struct scripted *scripted_value(XrAction action, XrPath sub)
{
unsigned i;
@@ -563,7 +581,9 @@ static void script_aim(const char *args)
{
char side[16];
float p[3], t[3], fx, fy, fz, len, yaw, pitch;
int hand;
float cy, sy, cp, sp, cr, sr;
float roll = 0.0f;
int hand, n;
if (sscanf(args, "%15s", side) != 1 || !(hand = parse_hand(side)))
return;
if (strstr(args, "off"))
@@ -571,8 +591,9 @@ static void script_aim(const char *args)
L.aim_set[hand] = false;
return;
}
if (sscanf(args, "%15s %f %f %f %f %f %f", side, &p[0], &p[1], &p[2],
&t[0], &t[1], &t[2]) != 7)
n = sscanf(args, "%15s %f %f %f %f %f %f %f", side, &p[0], &p[1], &p[2],
&t[0], &t[1], &t[2], &roll);
if (n != 7 && n != 8)
return;
fx = t[0] - p[0];
fy = t[1] - p[1];
@@ -582,11 +603,19 @@ static void script_aim(const char *args)
return;
yaw = atan2f(-fx / len, -fz / len);
pitch = asinf(fy / len);
/* Pitch about +X, then yaw about +Y: -Z points along (f). */
L.aim[hand].orientation.x = cosf(yaw / 2.0f) * sinf(pitch / 2.0f);
L.aim[hand].orientation.y = sinf(yaw / 2.0f) * cosf(pitch / 2.0f);
L.aim[hand].orientation.z = -sinf(yaw / 2.0f) * sinf(pitch / 2.0f);
L.aim[hand].orientation.w = cosf(yaw / 2.0f) * cosf(pitch / 2.0f);
roll = roll * 3.14159265f / 180.0f;
/* Yaw about +Y, pitch about +X, roll about the aim's own Z: -Z
* points along (f). */
cy = cosf(yaw / 2.0f);
sy = sinf(yaw / 2.0f);
cp = cosf(pitch / 2.0f);
sp = sinf(pitch / 2.0f);
cr = cosf(roll / 2.0f);
sr = sinf(roll / 2.0f);
L.aim[hand].orientation.x = cy * sp * cr + sy * cp * sr;
L.aim[hand].orientation.y = sy * cp * cr - cy * sp * sr;
L.aim[hand].orientation.z = cy * cp * sr - sy * sp * cr;
L.aim[hand].orientation.w = cy * cp * cr + sy * sp * sr;
L.aim[hand].position.x = p[0];
L.aim[hand].position.y = p[1];
L.aim[hand].position.z = p[2];
@@ -1527,6 +1556,18 @@ static XRAPI_ATTR XrResult XRAPI_CALL layer_WaitFrame(XrSession session,
return res;
}
/* RA_XR_LAYER_MAX_WIDTH: the widest swapchain the runtime says it
* makes, at most this. */
static XRAPI_ATTR XrResult XRAPI_CALL layer_GetSystemProperties(
XrInstance instance, XrSystemId system, XrSystemProperties *props)
{
XrResult res = L.GetSystemProperties(instance, system, props);
if ( XR_SUCCEEDED(res) && L.max_width
&& props->graphicsProperties.maxSwapchainImageWidth > L.max_width)
props->graphicsProperties.maxSwapchainImageWidth = L.max_width;
return res;
}
static XRAPI_ATTR XrResult XRAPI_CALL layer_EnumerateDisplayRefreshRatesFB(
XrSession session, uint32_t cap, uint32_t *count, float *rates)
{
@@ -1899,7 +1940,8 @@ static XRAPI_ATTR XrResult XRAPI_CALL layer_GetActionStateBoolean(XrSession sess
state->currentState = (s && s->v[0] != 0.0f) ? XR_TRUE : XR_FALSE;
state->changedSinceLastSync = XR_FALSE;
state->lastChangeTime = 0;
state->isActive = s ? XR_TRUE : XR_FALSE;
state->isActive = action_bound(gi->action) ? XR_TRUE
: XR_FALSE;
pthread_mutex_unlock(&L.lock);
return XR_SUCCESS;
}
@@ -1915,7 +1957,8 @@ static XRAPI_ATTR XrResult XRAPI_CALL layer_GetActionStateFloat(XrSession sessio
state->currentState = s ? s->v[0] : 0.0f;
state->changedSinceLastSync = XR_FALSE;
state->lastChangeTime = 0;
state->isActive = s ? XR_TRUE : XR_FALSE;
state->isActive = action_bound(gi->action) ? XR_TRUE
: XR_FALSE;
pthread_mutex_unlock(&L.lock);
return XR_SUCCESS;
}
@@ -1932,7 +1975,8 @@ static XRAPI_ATTR XrResult XRAPI_CALL layer_GetActionStateVector2f(XrSession ses
state->currentState.y = s ? s->v[1] : 0.0f;
state->changedSinceLastSync = XR_FALSE;
state->lastChangeTime = 0;
state->isActive = s ? XR_TRUE : XR_FALSE;
state->isActive = action_bound(gi->action) ? XR_TRUE
: XR_FALSE;
pthread_mutex_unlock(&L.lock);
return XR_SUCCESS;
}
@@ -2050,6 +2094,7 @@ static XRAPI_ATTR XrResult XRAPI_CALL layer_GetInstanceProcAddr(XrInstance insta
HOOK(WaitFrame)
HOOK(EndFrame)
HOOK(EnumerateViewConfigurationViews)
HOOK(GetSystemProperties)
HOOK(CreateActionSet)
HOOK(DestroyActionSet)
HOOK(CreateAction)
@@ -2117,6 +2162,7 @@ static XRAPI_ATTR XrResult XRAPI_CALL layer_CreateApiLayerInstance(
NEXT(BeginFrame);
NEXT(EndFrame);
NEXT(EnumerateViewConfigurationViews);
NEXT(GetSystemProperties);
NEXT(PathToString);
NEXT(CreateActionSet);
NEXT(DestroyActionSet);
+7 -1
View File
@@ -12,6 +12,8 @@ def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output-dir', type=Path, required=True)
parser.add_argument('--build-only', action='store_true')
parser.add_argument('--runtime', action='store_true',
help='build opt-in XR output test frontend')
parser.add_argument('--runner', help='executable runner, for example wine')
args = parser.parse_args()
root = Path(__file__).resolve().parents[3]
@@ -47,6 +49,10 @@ def main():
cmd[cmd.index(flag) + 1] = str(obj.with_suffix('.d') if flag == '-MF' else obj)
if rename:
cmd.append('-Dmain=rarch_harness_unused_main')
if args.runtime:
cmd.append('-Drunloop_iterate=video_screens_test_iterate')
elif args.runtime:
cmd.append('-DVIDEO_SCREENS_RUNTIME_TEST')
subprocess.run(cmd, cwd=root, check=True)
cmd = []
for token in link_cmd:
@@ -57,7 +63,7 @@ def main():
cmd[cmd.index('-o') + 1] = str(binary)
subprocess.run(cmd, cwd=root, check=True)
print('built ' + str(binary), flush=True)
if not args.build_only:
if not args.build_only and not args.runtime:
env = os.environ.copy()
for name, directory in (('XDG_CONFIG_HOME', 'config'), ('XDG_CACHE_HOME', 'cache'),
('XDG_DATA_HOME', 'data'), ('XDG_STATE_HOME', 'state')):
File diff suppressed because it is too large. Load diff
+35
View File
@@ -29,6 +29,11 @@ S_UINT_EX(video_openxr_placement, VIDEO_OPENXR_PLACEMENT,
0, SD_FLAG_NONE, SDESC_RANGE_MINMAX, 0, 0, 2, 1, 0, setting_action_ok_uint, setting_get_string_representation_video_openxr_placement, NULL, NULL, NULL, NULL, ST_UI_TYPE_UINT_COMBOBOX,
"Screen Placement",
"Where the headset keeps the screens. Fixed in Room: where Recenter puts them. Follow Head: always in front of you; on a large screen the edges sit where the lenses blur. Lazy Follow: they stay until you turn well away, then glide back in front of you.")
S_BOOL(video_openxr_menu_follow, VIDEO_OPENXR_MENU_FOLLOW,
"video_openxr_menu_follow",
false, SD_FLAG_NONE, 0, CMD_EVENT_NONE,
"Menu in Front of Screen 1",
"Opens the menu just in front of Screen 1, turned as it is, so moving Screen 1 moves the menu with it. Off: the menu opens straight ahead of the screens' anchor. A menu placed with Edit Screens stays where it was put.")
S_BOOL(video_openxr_perf_boost, VIDEO_OPENXR_PERF_BOOST,
"video_openxr_perf_boost",
false, SD_FLAG_NONE, 0, CMD_EVENT_NONE,
@@ -44,3 +49,33 @@ S_UINT_EX(video_openxr_laser, VIDEO_OPENXR_LASER,
0, SD_FLAG_NONE, SDESC_RANGE_MINMAX, 0, 0, 2, 1, 0, setting_action_ok_uint, setting_get_string_representation_video_openxr_laser, NULL, NULL, NULL, NULL, ST_UI_TYPE_UINT_COMBOBOX,
"Laser Pointer",
"Where a controller's trigger points and touches instead of pressing L2 or R2. Auto: the menu while it is open, and second screens such as a touch screen. Always: every screen, for light guns. Off: nowhere.")
S_UINT_EX(video_openxr_laser_beam, VIDEO_OPENXR_LASER_BEAM,
"video_openxr_laser_beam",
1, SD_FLAG_NONE, SDESC_RANGE_MINMAX, 0, 0, 2, 1, 0, setting_action_ok_uint, setting_get_string_representation_video_openxr_laser_beam, NULL, NULL, NULL, NULL, ST_UI_TYPE_UINT_COMBOBOX,
"Laser Beam",
"A thin line from each controller to where its laser points. Edit Mode Only: while Edit Screens moves the screens. Always: also for the menu and light guns. Off: never.")
S_BOOL_EX(video_openxr_edit_pause, VIDEO_OPENXR_EDIT_PAUSE,
"video_openxr_edit_pause",
false, SD_FLAG_NONE, 0, 0, NULL, setting_get_string_representation_video_openxr_edit_pause, NULL, NULL, NULL, NULL, 0,
"While Editing Screens",
"What the content does while Edit Screens moves the headset's screens: keeps running, or pauses until editing ends. Edit mode resumes only what it paused.")
S_BOOL(video_openxr_edit_snap, VIDEO_OPENXR_EDIT_SNAP,
"video_openxr_edit_snap",
true, SD_FLAG_NONE, 0, 0,
"Snap to Screens",
"Lines up a held screen with the screens near it: parallel or square to one, then in its plane and level, or edge to edge; or hinged to one along a shared edge at any angle. The holding hand buzzes as a snap takes hold.")
S_UINT_EX(video_openxr_edit_snap_angles, VIDEO_OPENXR_EDIT_SNAP_ANGLES,
"video_openxr_edit_snap_angles",
1, SD_FLAG_NONE, SDESC_RANGE_MINMAX, 0, 0, 4, 1, 0, setting_action_ok_uint, setting_get_string_representation_video_openxr_edit_snap_angles, NULL, NULL, NULL, NULL, ST_UI_TYPE_UINT_COMBOBOX,
"Snap to Angles",
"Stops a screen the other hand's stick turns or tilts at every step of this angle, with a buzz: turns count from facing you, tilts from upright, and a hinged screen from flat with its neighbour. Snap to Screens wins near another screen. Off: the stick turns and tilts smoothly.")
S_UINT_EX(video_openxr_edit_hinge_gap, VIDEO_OPENXR_EDIT_HINGE_GAP,
"video_openxr_edit_hinge_gap",
2, SD_FLAG_NONE, SDESC_RANGE_MINMAX, 0, 0, 5, 1, 0, setting_action_ok_uint, setting_get_string_representation_video_openxr_edit_hinge_gap, NULL, NULL, NULL, NULL, ST_UI_TYPE_UINT_COMBOBOX,
"Hinge Gap",
"Leaves this gap between screens that meet edge to edge, side by side, at a corner or hinged at an angle. A screen clicks to it within 1 cm; elsewhere up to 10 cm the gap stays where you put it. 0: the edges touch.")
S_UINT_EX(video_openxr_edit_smoothing, VIDEO_OPENXR_EDIT_SMOOTHING,
"video_openxr_edit_smoothing",
2, SD_FLAG_NONE, SDESC_RANGE_MINMAX, 0, 0, 3, 1, 0, setting_action_ok_uint, setting_get_string_representation_video_openxr_edit_smoothing, NULL, NULL, NULL, NULL, ST_UI_TYPE_UINT_COMBOBOX,
"Edit Smoothing",
"Steadies the controllers' aim while Edit Screens moves the screens, so a screen held far out does not shake with the hand: a slow hand is smoothed a lot, a fast one little. Off: the aim as tracked.")
+57
View File
@@ -0,0 +1,57 @@
/* Indexed menu templates; configuration uses the indexed XR passes. */
S_ACTION(VIDEO_SCREEN_SELECTED,
"video_screen_selected",
"Screen",
"Select the floating screen to adjust.")
S_FLOAT(video_screen_xr_x[0], VIDEO_SCREEN_XR_X,
"video_screen_xr_x",
0.0f, "%.1f m", SD_FLAG_NONE, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, -10, 10, .1, NULL, NULL,
"Horizontal Offset",
"Move this screen left or right in metres.")
S_FLOAT(video_screen_xr_y[0], VIDEO_SCREEN_XR_Y,
"video_screen_xr_y",
0.0f, "%.1f m", SD_FLAG_NONE, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, -10, 10, .1, NULL, NULL,
"Vertical Offset",
"Move this screen up or down in metres.")
S_FLOAT(video_screen_xr_z[0], VIDEO_SCREEN_XR_Z,
"video_screen_xr_z",
0.0f, "%.1f m", SD_FLAG_NONE, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, -10, 10, .1, NULL, NULL,
"Depth Offset",
"Move this screen towards or away from you in metres.")
S_FLOAT(video_screen_xr_yaw[0], VIDEO_SCREEN_XR_YAW,
"video_screen_xr_yaw",
0.0f, "%.0f deg", SD_FLAG_NONE, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, -180, 180, 1, NULL, NULL,
"Yaw",
"Turn this screen left or right in degrees.")
S_FLOAT(video_screen_xr_pitch[0], VIDEO_SCREEN_XR_PITCH,
"video_screen_xr_pitch",
0.0f, "%.0f deg", SD_FLAG_NONE, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, -180, 180, 1, NULL, NULL,
"Pitch",
"Tilt this screen up or down in degrees.")
S_FLOAT(video_screen_xr_roll[0], VIDEO_SCREEN_XR_ROLL,
"video_screen_xr_roll",
0.0f, "%.0f deg", SD_FLAG_NONE, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, -180, 180, 1, NULL, NULL,
"Roll",
"Rotate this screen in degrees.")
S_FLOAT(video_screen_xr_scale[0], VIDEO_SCREEN_XR_SCALE,
"video_screen_xr_scale",
1.0f, "%.1f", SD_FLAG_NONE, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, 0.1, 10, .1, NULL, NULL,
"Scale",
"Resize this floating screen.")
S_BOOL(video_screen_xr_visible[0], VIDEO_SCREEN_XR_VISIBLE,
"video_screen_xr_visible",
true, SD_FLAG_NONE, 0, 0,
"Visible",
"Show this floating screen. The menu remains available when screens are hidden.")
S_ACTION(VIDEO_SCREEN_XR_RESET,
"video_screen_xr_reset",
"Reset Screen",
"Restore the selected floating screen's position, rotation, scale and visibility.")
S_ACTION(VIDEO_SCREEN_XR_RESET_ALL,
"video_screen_xr_reset_all",
"Reset All Screens",
"Restore every screen's position, rotation, scale and visibility, including screens outside the current map, and return the menu to where it opens unplaced.")
S_ACTION(VIDEO_SCREEN_XR_EDIT,
"video_screen_xr_edit",
"Edit Screens",
"Close the menu and move the headset's screens and menu with the controllers: grip grabs, the trigger tilts, the stick changes distance and size, the free hand's stick turns and tilts, the main button resets, Select Next and Select Previous choose an item.")
+21
View File
@@ -202,7 +202,10 @@ bool video_hdr_scanlines 1 1
bool video_stereo_swap_eyes 1 0
bool video_windows_enable 1 1
bool video_openxr_enable 1 0
bool video_openxr_menu_follow 1 0
bool video_openxr_perf_boost 1 0
bool video_openxr_edit_pause 1 0
bool video_openxr_edit_snap 1 1
bool rewind_enable 1 0
bool menu_linear_filter 1 0
bool xmb_vertical_thumbnails 1 0
@@ -577,6 +580,10 @@ uint video_openxr_refresh_rate 1 0
uint video_openxr_placement 1 0
uint video_openxr_controllers 1 0
uint video_openxr_laser 1 0
uint video_openxr_laser_beam 1 1
uint video_openxr_edit_snap_angles 1 1
uint video_openxr_edit_hinge_gap 1 2
uint video_openxr_edit_smoothing 1 2
uint rewind_granularity 1 1
uint playlist_show_history_icons 1 PLAYLIST_SHOW_HISTORY_ICONS_CONTENT
uint rgui_internal_upscale_level 1 RGUI_UPSCALE_NONE
@@ -925,7 +932,10 @@ bool ozone_collapse_sidebar 1 0
bool video_hdr_scanlines 1 1
bool video_stereo_swap_eyes 1 0
bool video_openxr_enable 1 0
bool video_openxr_menu_follow 1 0
bool video_openxr_perf_boost 1 0
bool video_openxr_edit_pause 1 0
bool video_openxr_edit_snap 1 1
bool rewind_enable 1 0
bool menu_linear_filter 1 0
bool xmb_vertical_thumbnails 1 0
@@ -1295,6 +1305,10 @@ uint video_openxr_refresh_rate 1 0
uint video_openxr_placement 1 0
uint video_openxr_controllers 1 0
uint video_openxr_laser 1 0
uint video_openxr_laser_beam 1 1
uint video_openxr_edit_snap_angles 1 1
uint video_openxr_edit_hinge_gap 1 2
uint video_openxr_edit_smoothing 1 2
uint rewind_granularity 1 1
uint playlist_show_history_icons 1 PLAYLIST_SHOW_HISTORY_ICONS_CONTENT
uint rgui_internal_upscale_level 1 RGUI_UPSCALE_NONE
@@ -1648,7 +1662,10 @@ bool ozone_collapse_sidebar 1 0
bool video_hdr_scanlines 1 1
bool video_stereo_swap_eyes 1 0
bool video_openxr_enable 1 0
bool video_openxr_menu_follow 1 0
bool video_openxr_perf_boost 1 0
bool video_openxr_edit_pause 1 0
bool video_openxr_edit_snap 1 1
bool rewind_enable 1 0
bool menu_linear_filter 1 0
bool xmb_vertical_thumbnails 1 0
@@ -2034,6 +2051,10 @@ uint video_openxr_refresh_rate 1 0
uint video_openxr_placement 1 0
uint video_openxr_controllers 1 0
uint video_openxr_laser 1 0
uint video_openxr_laser_beam 1 1
uint video_openxr_edit_snap_angles 1 1
uint video_openxr_edit_hinge_gap 1 2
uint video_openxr_edit_smoothing 1 2
uint rewind_granularity 1 1
uint playlist_show_history_icons 1 PLAYLIST_SHOW_HISTORY_ICONS_CONTENT
uint rgui_internal_upscale_level 1 RGUI_UPSCALE_NONE