Add VR support to Android

This commit is contained in:
Craig Carnell committed 2026-10-06 09:08:15 +01:00
1 parent 1aab50c25d
commit 4ff056478c
6 files changed
+829 -12

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
*.o
libs/
obj/
.vscode/
+582
View File
@@ -2871,6 +2871,336 @@ enum retro_mod
*/
#define RETRO_ENVIRONMENT_GET_HDR_MAX_NITS (92 | RETRO_ENVIRONMENT_EXPERIMENTAL)
/**
* Negotiates multi-channel audio output.
*
* The classic batch callbacks carry interleaved stereo. A core whose
* source has more channels - a console with discrete surround, a
* media player, an arcade board with a distinct rear pair - has had
* to fold them to two at the libretro boundary. This call hands the
* core a pair of batch entry points that take a frame of any of the
* layouts below, so the channels reach the frontend as they are;
* what happens to them then is the frontend's: sent discretely to a
* device that has those speakers, folded to stereo for one that does
* not, folded and re-expanded as the user's settings say.
*
* On success the frontend fills the supplied
* \c retro_audio_sample_multi_callback: \c batch_int16 always, and
* \c batch_float when it also answers \c true to
* \c RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_FLOAT (a core wanting
* float should query that first; a NULL \c batch_float means int16
* only). Either function takes interleaved frames of \c channels
* samples, in the ascending-bit order of \c layout - front left,
* front right, front centre, LFE, back left, back right, ...
* - which is the order the WAVEFORMATEXTENSIBLE channel mask, ALSA,
* SDL and the WAV format use. \c channels must equal the number of
* bits set in \c layout. The return value has the meaning of
* \c retro_audio_sample_batch_t.
*
* Contract:
* - Negotiate once, during \c retro_load_game(). The layout may
* change from call to call (a game switching from stereo to 5.1),
* but the core commits to one sample format for the loaded game,
* as with the float call, and does not mix these entry points
* with the classic ones.
* - A layout with a bit the frontend does not know, or more than
* eight channels, is refused: the call returns 0 frames. Cores
* should use the \c RETRO_AUDIO_LAYOUT_ constants.
* - The function pointers are owned by the frontend and remain
* valid until \c retro_unload_game().
* - Frontends that do not recognise this call return \c false; the
* core keeps folding to stereo and using the classic callbacks.
*
* @param[out] data <tt>struct retro_audio_sample_multi_callback *</tt>.
* @return \c true if multi-channel output is supported, \c false otherwise.
* @see retro_audio_sample_multi_callback
* @see RETRO_AUDIO_SPEAKER_FRONT_LEFT
*/
#define RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_MULTI (94 | RETRO_ENVIRONMENT_EXPERIMENTAL)
/**
* Notifies the frontend that this core can render stereoscopic VR content
* and describes what it needs from the frontend to do so.
*
* Should be called from retro_load_game(), after RETRO_ENVIRONMENT_SET_HW_RENDER
* if hardware rendering is used. VR content requires hardware rendering in
* practice, since the frontend provides a hardware framebuffer containing
* both eye images.
*
* A core that supports both a flat and a VR presentation of the same
* content should call this unconditionally and let the frontend's return
* value decide: returning false means no VR session is available (headset
* build not active, or the platform does not support VR), in which case
* the core continues exactly as if the call had never been made.
*
* When VR is requested successfully, the core must report its VR framebuffer
* geometry through retro_get_system_av_info(): the base and maximum geometry
* must both be 2W by H, where W and H are the dimensions of one eye.
*
* The frontend uses this geometry when allocating the hardware framebuffer;
* recommended_eye_width and recommended_eye_height do not determine the
* framebuffer allocation.
*
* If RETRO_VR_FRAME_TARGET_RESIZED is subsequently reported, the eye target
* dimensions have changed. The core must call this environment command again
* and update its system AV info so that the base and maximum geometry are
* again 2W by H. Otherwise the new stereo framebuffer may exceed the
* currently allocated maximum geometry.
*
* The frontend samples the eye poses and field of view once per frame and
* passes that same sample to the core through
* RETRO_ENVIRONMENT_GET_VR_FRAME_STATE. When submitting the compositor
* layer for that frame, the frontend must use the same eye poses and FOV
* that were supplied to the core. This keeps the compositor submission
* synchronized with the sample used to render the eye images and avoids
* reprojection errors caused by using a different pose sample.
*
* @param[in,out] data <tt>struct retro_vr_content_info *</tt>.
* If non-NULL, requests VR presentation using the supplied content
* information. If NULL, disables the current VR presentation.
*
* @returns true if the requested operation was accepted and successfully
* applied, false if VR is unavailable or the request cannot be satisfied.
*
* @see retro_vr_content_info
* @see RETRO_ENVIRONMENT_GET_VR_FRAME_STATE
*/
#define RETRO_ENVIRONMENT_SET_VR_CONTENT_INFO (95 | RETRO_ENVIRONMENT_EXPERIMENTAL)
enum retro_vr_layout {
/* Core renders both eyes into the frontend framebuffer (the one from
* retro_hw_render_callback::get_current_framebuffer), left eye in
* x=[0,W), right eye in x=[W,2W), and calls retro_video_refresh_t ONCE per
* retro_run() with RETRO_HW_FRAME_BUFFER_VALID, width=2W, height=H.
* Origin follows hw_render.bottom_left_origin. Alpha is ignored. */
RETRO_VR_LAYOUT_SIDE_BY_SIDE = 0,
RETRO_VR_LAYOUT_DUMMY = INT_MAX
};
enum retro_vr_reference_space {
RETRO_VR_REFERENCE_SPACE_LOCAL = 0, /* seated: origin = head at session start/recenter */
RETRO_VR_REFERENCE_SPACE_STAGE = 1, /* standing: origin = floor */
RETRO_VR_REFERENCE_SPACE_DUMMY = INT_MAX
};
/**
* Details a core provides when requesting a VR session via
* RETRO_ENVIRONMENT_SET_VR_CONTENT_INFO.
*/
struct retro_vr_content_info
{
/**
* true if the core renders both eyes natively into the VR
* framebuffer using the layout specified by layout
*
* false if the core renders mono content and the frontend
* derives the stereo presentation.
*/
bool stereo_native;
/**
* A core has requested a flat presentation of its content, even if it
* is capable of producing VR output.
*/
bool request_flat;
/**
* Requested interpupillary distance in meters, used only if the
* frontend cannot obtain one from the runtime/HMD itself.
*
* 0.0f means "use whatever the frontend/runtime already knows".
*/
float ipd_hint_m;
enum retro_vr_layout layout;
enum retro_vr_reference_space reference_space;
/**
* Output: recommended width of one eye's render target.
*
* The core must not use this value to determine its system AV geometry.
* The VR framebuffer geometry is reported separately through
* retro_get_system_av_info() as 2W x H.
*/
unsigned recommended_eye_width;
/**
* Output: recommended height of one eye's render target.
*/
unsigned recommended_eye_height;
};
#define RETRO_VR_FRAME_RECENTERED (1u << 0) /* user recentered: re-capture any reference */
#define RETRO_VR_FRAME_TARGET_RESIZED (1u << 1) /* eye size changed: call SET_VR_CONTENT_INFO again */
/**
* Retrieves the per-eye state plus per-frame event flags
* (RETRO_VR_FRAME_RECENTERED / RETRO_VR_FRAME_TARGET_RESIZED) for the
* current retro_run() call.
*
* The frontend samples both eye states once per frame, immediately before
* retro_run(), and returns that same sample for the duration of that
* retro_run() call.
*
* The position and orientation in each eye state are the eye-to-tracking
* transforms used for that frame. The FOV values are the same values the
* frontend must use when submitting the compositor layer for the
* corresponding frame.
*
* @param[out] data <tt>struct retro_vr_frame_state *</tt>.
* @return false if no VR session exists or no valid sample is available.
*/
#define RETRO_ENVIRONMENT_GET_VR_FRAME_STATE (96 | RETRO_ENVIRONMENT_EXPERIMENTAL)
/**
* Identifies which eye a \ref retro_vr_eye_state describes, and the index
* of that eye within the two-element array passed to
* RETRO_ENVIRONMENT_GET_VR_FRAME_STATE.
*/
enum retro_vr_eye
{
RETRO_VR_EYE_LEFT = 0,
RETRO_VR_EYE_RIGHT = 1,
/** @private Defined to ensure <tt>sizeof(retro_vr_eye) == sizeof(int)</tt>. Do not use. */
RETRO_VR_EYE_DUMMY = INT_MAX
};
/**
* One eye's head-tracked pose and asymmetric field of view for the current
* frame, in the frontend's tracking space (meters, right-handed, Y up).
*
* position and orientation describe the eye-to-tracking-space transform,
* not its inverse.
*
* The quaternion is stored in (x, y, z, w) order. This is intentionally
* different from APIs such as GLM where glm::quat is constructed as
* (w, x, y, z).
*
* @see RETRO_ENVIRONMENT_GET_VR_FRAME_STATE
*/
struct retro_vr_eye_state
{
/**
* Eye position in tracking space, in meters.
*/
float position[3];
/**
* Eye orientation in tracking space, as a quaternion (x, y, z, w).
*
* This is the orientation of the eye relative to tracking space, not
* the inverse/view orientation.
*/
float orientation[4];
/**
* Tangent of the positive half-angle from the eye's forward axis to
* each frustum edge, in the order:
*
* [0] left
* [1] right
* [2] up
* [3] down
*
* All four values are positive magnitudes. They describe the distance
* of each frustum edge from the forward axis in tangent space; they are
* not signed OpenXR angles.
*
* The four values are independent because HMD eye frustums are normally
* asymmetric.
*
* For example, if the left and right tangent values are l and r, and
* the up and down tangent values are u and d, the projection frustum
* spans:
*
* left = -l
* right = r
* bottom = -d
* top = u
*
* A frontend converting from APIs that represent the corresponding
* angles as signed values must use their magnitudes.
*/
float fov_tan[4];
};
struct retro_vr_frame_state {
struct retro_vr_eye_state eyes[2];
uint32_t flags;
};
/**
* Retrieves the current head-tracked pose.
*
* The frontend samples the HMD once per frame, immediately before
* retro_run(), and returns the same sample for the duration of that
* retro_run() call.
*/
#define RETRO_ENVIRONMENT_GET_VR_HEAD_POSE (97 | RETRO_ENVIRONMENT_EXPERIMENTAL)
#define RETRO_VR_HEAD_POSE_POSITION_VALID (1u << 0)
#define RETRO_VR_HEAD_POSE_ORIENTATION_VALID (1u << 1)
#define RETRO_VR_HEAD_POSE_VELOCITY_VALID (1u << 2)
/**
* Head pose and motion in the frontend's tracking space (meters,
* seconds, right-handed, Y up).
*
* position and orientation describe the head-to-tracking-space transform,
* not its inverse. The orientation quaternion rotates vectors from head
* space into tracking space.
*
* linear_velocity is the velocity of the head-space origin, expressed in
* tracking-space coordinates. angular_velocity is the head's angular
* velocity, expressed in tracking-space coordinates. Velocity fields are
* valid only when RETRO_VR_HEAD_POSE_VELOCITY_VALID is set.
*/
struct retro_vr_head_pose
{
/** Head position in tracking space, in meters. */
float position[3];
/** Head orientation in tracking space, quaternion (x, y, z, w). */
float orientation[4];
/** Linear velocity in meters/second. */
float linear_velocity[3];
/** Angular velocity in radians/second. */
float angular_velocity[3];
/* orientation/position are valid */
uint32_t flags;
};
/* Speaker positions, as bits of a layout mask; a frame's channels are
* interleaved in ascending bit order. The bits are those of the
* WAVEFORMATEXTENSIBLE channel mask. */
#define RETRO_AUDIO_SPEAKER_FRONT_LEFT 0x001
#define RETRO_AUDIO_SPEAKER_FRONT_RIGHT 0x002
#define RETRO_AUDIO_SPEAKER_FRONT_CENTER 0x004
#define RETRO_AUDIO_SPEAKER_LOW_FREQUENCY 0x008
#define RETRO_AUDIO_SPEAKER_BACK_LEFT 0x010
#define RETRO_AUDIO_SPEAKER_BACK_RIGHT 0x020
#define RETRO_AUDIO_SPEAKER_FRONT_LEFT_OF_CENTER 0x040
#define RETRO_AUDIO_SPEAKER_FRONT_RIGHT_OF_CENTER 0x080
#define RETRO_AUDIO_SPEAKER_BACK_CENTER 0x100
#define RETRO_AUDIO_SPEAKER_SIDE_LEFT 0x200
#define RETRO_AUDIO_SPEAKER_SIDE_RIGHT 0x400
/* The layouts a core is expected to use. Others are accepted where the
* frontend knows every bit. */
#define RETRO_AUDIO_LAYOUT_MONO (RETRO_AUDIO_SPEAKER_FRONT_CENTER)
#define RETRO_AUDIO_LAYOUT_STEREO (RETRO_AUDIO_SPEAKER_FRONT_LEFT | RETRO_AUDIO_SPEAKER_FRONT_RIGHT)
#define RETRO_AUDIO_LAYOUT_2_1 (RETRO_AUDIO_LAYOUT_STEREO | RETRO_AUDIO_SPEAKER_LOW_FREQUENCY)
#define RETRO_AUDIO_LAYOUT_QUAD (RETRO_AUDIO_LAYOUT_STEREO | RETRO_AUDIO_SPEAKER_BACK_LEFT | RETRO_AUDIO_SPEAKER_BACK_RIGHT)
#define RETRO_AUDIO_LAYOUT_5_1 (RETRO_AUDIO_LAYOUT_QUAD | RETRO_AUDIO_SPEAKER_FRONT_CENTER | RETRO_AUDIO_SPEAKER_LOW_FREQUENCY)
#define RETRO_AUDIO_LAYOUT_5_1_SIDE (RETRO_AUDIO_LAYOUT_STEREO | RETRO_AUDIO_SPEAKER_FRONT_CENTER | RETRO_AUDIO_SPEAKER_LOW_FREQUENCY \
| RETRO_AUDIO_SPEAKER_SIDE_LEFT | RETRO_AUDIO_SPEAKER_SIDE_RIGHT)
#define RETRO_AUDIO_LAYOUT_7_1 (RETRO_AUDIO_LAYOUT_5_1 | RETRO_AUDIO_SPEAKER_SIDE_LEFT | RETRO_AUDIO_SPEAKER_SIDE_RIGHT)
/**
* Result of \c RETRO_ENVIRONMENT_GET_MEMORY_STATUS.
*
@@ -3052,8 +3382,49 @@ struct retro_vfs_dir_handle;
*/
#define RETRO_VFS_STAT_IS_CHARACTER_SPECIAL (1 << 2)
/**
* Indicates that the current user cannot write to the given path.
* POSIX: the owner write bit is clear.
* Windows/UWP: \c FILE_ATTRIBUTE_READONLY is set.
* Frontends that cannot determine this never set the flag.
* @since VFS API v5
*/
#define RETRO_VFS_STAT_IS_READONLY (1 << 3)
/** @} */
/**
* @defgroup RETRO_VFS_COPY Copy Flags
* @since VFS API v5
* @{
*/
/** Replace \c dst if it already exists. Without it an existing \c dst is an error. */
#define RETRO_VFS_COPY_OVERWRITE (1 << 0)
/** @} */
/**
* @defgroup RETRO_VFS_COPY_STATUS Copy Status
* Values returned by \c retro_vfs_copy_step_t.
* @since VFS API v5
* @{
*/
/** The copy is still in progress. */
#define RETRO_VFS_COPY_RUNNING (0)
/** The copy completed; \c dst is complete and closed. */
#define RETRO_VFS_COPY_DONE (1)
/** The copy failed or was cancelled; no partial \c dst remains. */
#define RETRO_VFS_COPY_FAILED (-1)
/** @} */
/**
* Opaque handle to an in-progress file copy.
* @see retro_vfs_copy_begin_t
* @since VFS API v5
*/
struct retro_vfs_copy_handle;
/**
* Returns the path that was used to open this file.
*
@@ -3245,6 +3616,124 @@ typedef int (RETRO_CALLCONV *retro_vfs_stat_t)(const char *path, int32_t *size);
*/
typedef int (RETRO_CALLCONV *retro_vfs_stat_64_t)(const char *path, int64_t *size);
/**
* Sets or clears the read-only state of a file or directory.
*
* POSIX: sets or clears the write bits of the mode, leaving the rest intact.
* Windows/UWP: sets or clears \c FILE_ATTRIBUTE_READONLY.
*
* @param path The path to the file or directory.
* @param readonly Non-zero to make the path read-only,
* zero to make it writable.
* @return 0 on success,
* or -1 if \c path does not exist or the platform or file system
* cannot store a read-only state.
* @see path_set_readonly
* @see RETRO_VFS_STAT_IS_READONLY
* @since VFS API v5
*/
typedef int (RETRO_CALLCONV *retro_vfs_set_readonly_t)(const char *path, int readonly);
/**
* Gets the last modification time of a file or directory.
*
* @param path The path to the file or directory.
* @param[out] mtime Set to the modification time
* in seconds since 1970-01-01T00:00:00Z. May be negative.
* @return 0 on success,
* or -1 if \c path does not exist or the platform
* cannot report a modification time.
* @see path_get_mtime
* @since VFS API v5
*/
typedef int (RETRO_CALLCONV *retro_vfs_get_mtime_t)(const char *path, int64_t *mtime);
/**
* Sets the last modification time of a file or directory.
*
* The frontend rounds to the file system's resolution,
* so a following \c retro_vfs_get_mtime_t may report a different value.
*
* @param path The path to the file or directory.
* @param mtime The modification time in seconds since 1970-01-01T00:00:00Z.
* @return 0 on success,
* or -1 if \c path does not exist or the platform or file system
* does not allow the modification time to be set.
* @see path_set_mtime
* @since VFS API v5
*/
typedef int (RETRO_CALLCONV *retro_vfs_set_mtime_t)(const char *path, int64_t mtime);
/**
* Starts copying a single regular file and returns without moving any of it.
*
* A copy is a resumable operation that the caller advances with
* \c retro_vfs_copy_step_t, each step bounded by a byte budget the caller
* chooses. The frontend keeps no thread and holds no lock for it; a caller
* that wants the transfer off its own thread drives the steps from wherever
* it likes. No call in this group ever waits for more than the requested
* step.
*
* \c dst is the full path of the new file, not a directory; missing parent
* directories are created. Metadata (modification time, read-only state)
* of \c dst after the copy is platform-defined. Either path may belong to
* any file system the frontend supports.
*
* Checks that can be made up front (missing or non-regular \c src,
* \c dst is a directory, \c dst exists without \c RETRO_VFS_COPY_OVERWRITE,
* \c src equals \c dst) fail here by returning \c NULL.
*
* @param src The path to the file to copy. Must be a regular file.
* @param dst The full path of the destination file. Must differ from \c src.
* @param flags Bitwise combination of \c RETRO_VFS_COPY flags, or 0.
* @return A handle to poll and close, or \c NULL if the copy could not start.
* @see retro_vfs_copy_step_t
* @see retro_vfs_copy_close_t
* @see filestream_copy_begin
* @see RETRO_VFS_COPY
* @since VFS API v5
*/
typedef struct retro_vfs_copy_handle *(RETRO_CALLCONV *retro_vfs_copy_begin_t)(const char *src, const char *dst, unsigned flags);
/**
* Advances a copy started with \c retro_vfs_copy_begin_t by at most
* \c max_bytes and reports its state.
*
* The budget is the caller's latency/throughput dial: a few MiB from a
* frame loop keeps each call short; a very large budget (or repeated calls
* until the status leaves \c RETRO_VFS_COPY_RUNNING) runs the transfer at
* the full speed of the platform's copy primitive with no user-space
* buffer where the kernel can move the bytes itself.
*
* A step never moves more than \c max_bytes, but it may move less, and it
* may report \c RETRO_VFS_COPY_DONE early if the platform completed the
* copy without moving bytes (e.g. a file-system clone).
*
* @param handle The copy.
* @param max_bytes Upper bound on bytes moved by this call; 0 selects a
* frontend default sized for a frame loop (a few MiB).
* @param[out] bytes_done Bytes written to \c dst so far. May be \c NULL.
* @param[out] bytes_total Size of \c src in bytes. May be \c NULL.
* @return One of the \c RETRO_VFS_COPY_STATUS values.
* @since VFS API v5
*/
typedef int (RETRO_CALLCONV *retro_vfs_copy_step_t)(struct retro_vfs_copy_handle *handle, int64_t max_bytes, int64_t *bytes_done, int64_t *bytes_total);
/**
* Releases a copy handle.
*
* If the copy is still running it is cancelled and the partial \c dst
* removed; nothing is waited for. Must be called exactly once for every
* non-NULL handle from \c retro_vfs_copy_begin_t, whatever
* \c retro_vfs_copy_step_t reported.
*
* @param handle The copy.
* @return 0 if the copy had completed successfully, or -1 if it failed,
* was cancelled, or was still running when closed.
* @since VFS API v5
*/
typedef int (RETRO_CALLCONV *retro_vfs_copy_close_t)(struct retro_vfs_copy_handle *handle);
/**
* Creates a directory at the given path.
*
@@ -3317,6 +3806,29 @@ typedef const char *(RETRO_CALLCONV *retro_vfs_dirent_get_name_t)(struct retro_v
*/
typedef bool (RETRO_CALLCONV *retro_vfs_dirent_is_dir_t)(struct retro_vfs_dir_handle *dirstream);
/**
* Gets information about the directory entry most recently returned by
* \c retro_vfs_readdir_t, without opening it or building its path.
*
* Only valid after a \c retro_vfs_readdir_t call that returned \c true,
* and before the next \c retro_vfs_readdir_t or \c retro_vfs_closedir_t
* call on the same handle.
*
* @param dirstream The directory being enumerated.
* @param[out] size The entry's size in bytes (0 for directories).
* May be \c NULL, in which case this value is ignored.
* @param[out] mtime The entry's modification time
* in seconds since 1970-01-01T00:00:00Z.
* May be \c NULL, in which case this value is ignored.
* @return A bitmask of \c RETRO_VFS_STAT flags for the entry
* (\c RETRO_VFS_STAT_IS_VALID is always set on success),
* or 0 if the frontend cannot provide entry information.
* @see retro_dirent_stat
* @see RETRO_VFS_STAT
* @since VFS API v5
*/
typedef int (RETRO_CALLCONV *retro_vfs_dirent_stat_t)(struct retro_vfs_dir_handle *dirstream, int64_t *size, int64_t *mtime);
/**
* Closes the given directory and release its resources.
*
@@ -3404,6 +3916,28 @@ struct retro_vfs_interface
/* VFS API v4 */
/** @copydoc retro_vfs_stat_64_t */
retro_vfs_stat_64_t stat_64;
/* VFS API v5 */
/** @copydoc retro_vfs_set_readonly_t */
retro_vfs_set_readonly_t set_readonly;
/** @copydoc retro_vfs_get_mtime_t */
retro_vfs_get_mtime_t get_mtime;
/** @copydoc retro_vfs_set_mtime_t */
retro_vfs_set_mtime_t set_mtime;
/** @copydoc retro_vfs_copy_begin_t */
retro_vfs_copy_begin_t copy_begin;
/** @copydoc retro_vfs_copy_step_t */
retro_vfs_copy_step_t copy_step;
/** @copydoc retro_vfs_copy_close_t */
retro_vfs_copy_close_t copy_close;
/** @copydoc retro_vfs_dirent_stat_t */
retro_vfs_dirent_stat_t dirent_stat;
};
/**
@@ -3755,6 +4289,20 @@ enum retro_hw_render_context_negotiation_interface_type
*/
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN = 0,
/**
* Denotes a context negotiation interface for Direct3D 12.
* Carries the highest hardware render interface version the core can use.
* @see retro_hw_render_context_negotiation_interface_d3d12
*/
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D12 = 1,
/**
* Denotes a context negotiation interface for Direct3D 11.
* Carries the highest hardware render interface version the core can use.
* @see retro_hw_render_context_negotiation_interface_d3d11
*/
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11 = 2,
/**
* @private Defined to ensure <tt>sizeof(retro_hw_render_context_negotiation_interface_type) == sizeof(int)</tt>.
* Do not use.
@@ -8021,6 +8569,40 @@ struct retro_audio_sample_float_callback
retro_audio_sample_batch_float_t batch;
};
/**
* Renders multiple audio frames of a multi-channel layout.
*
* Valid only after the frontend has answered \c true to
* \c RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_MULTI.
*
* @param data Interleaved frames of \c channels samples, one sample
* a speaker in the ascending-bit order of \c layout; int16 or
* float in [-1.0, 1.0] by the entry point.
* @param frames The number of frames in \c data.
* @param channels Samples per frame: the bits set in \c layout.
* @param layout The speaker mask, from the \c RETRO_AUDIO_SPEAKER_ bits.
* @return The number of frames processed; 0 for a layout the frontend
* does not take.
* @see RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_MULTI
*/
typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_multi_int16_t)(
const int16_t *data, size_t frames, unsigned channels, unsigned layout);
typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_multi_float_t)(
const float *data, size_t frames, unsigned channels, unsigned layout);
/**
* Multi-channel batch callbacks handed to the core in response to
* \c RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_MULTI.
*/
struct retro_audio_sample_multi_callback
{
/* Set by the frontend. */
retro_audio_sample_batch_multi_int16_t batch_int16;
/* Set by the frontend when float output is negotiated too, NULL
* otherwise. */
retro_audio_sample_batch_multi_float_t batch_float;
};
/**
* Polls input.
*
+66
View File
@@ -71,6 +71,11 @@ unsigned engine_height = BASE_HEIGHT;
GLuint tex;
GLuint g_texture_target = GL_TEXTURE_2D;
bool vr_active = false;
unsigned vr_eye_width = 0;
unsigned vr_eye_height = 0;
bool vr_user_enable = true;
void retro_init(void)
{
struct retro_log_callback log;
@@ -119,6 +124,13 @@ void retro_get_system_av_info(struct retro_system_av_info *info)
info->geometry.base_height = BASE_HEIGHT;
info->geometry.max_width = MAX_WIDTH;
info->geometry.max_height = MAX_HEIGHT;
if (vr_active)
{
info->geometry.base_width = info->geometry.max_width = vr_eye_width * 2;
info->geometry.base_height = info->geometry.max_height = vr_eye_height;
info->geometry.aspect_ratio = (float)(vr_eye_width * 2) / vr_eye_height;
}
}
void retro_set_environment(retro_environment_t cb)
@@ -158,6 +170,7 @@ void retro_set_environment(retro_environment_t cb)
},
{ "3dengine-modelviewer-discard-hack", "Discard hack enable; disabled|enabled" },
{ "3dengine-location-display-position", "Location position OSD; disabled|enabled" },
{ "3dengine-modelviewer-vr-enable", "VR enable in modelviewer; enabled|disabled" },
{ NULL, NULL },
};
@@ -404,6 +417,53 @@ static void location_deinitialized(void)
location_cb.stop();
}
// VAR
static bool vr_request_session(void)
{
struct retro_vr_content_info vr;
memset(&vr, 0, sizeof(vr));
vr.stereo_native = true;
vr.ipd_hint_m = 0.0f;
vr.layout = RETRO_VR_LAYOUT_SIDE_BY_SIDE;
vr.request_flat = !vr_user_enable;
vr.reference_space = RETRO_VR_REFERENCE_SPACE_LOCAL; /* eye height == player_pos.y */
if (!environ_cb(RETRO_ENVIRONMENT_SET_VR_CONTENT_INFO, &vr))
{
log_cb(RETRO_LOG_INFO, "VR session request failed.\n");
return false; /* old frontend, no headset, or VR disabled -> flat */
}
vr_eye_width = vr.recommended_eye_width ? vr.recommended_eye_width : 1024;
vr_eye_height = vr.recommended_eye_height ? vr.recommended_eye_height : 1024;
log_cb(RETRO_LOG_INFO, "VR session request success. vr_eye_width: %d vr_eye_height: %d.\n", vr_eye_width, vr_eye_height);
return true;
}
bool vr_poll_frame(struct retro_vr_frame_state *fs)
{
struct retro_vr_frame_state tmp = *fs;
if (!environ_cb(RETRO_ENVIRONMENT_GET_VR_FRAME_STATE, &tmp))
return false; /* keep last pose */
*fs = tmp;
if (tmp.flags & RETRO_VR_FRAME_TARGET_RESIZED)
{
if (vr_request_session())
{
struct retro_system_av_info av;
retro_get_system_av_info(&av);
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av);
}
else
vr_active = false;
}
return true;
}
// END VR
bool retro_load_game(const struct retro_game_info *info)
{
retro_variable var;
@@ -544,6 +604,11 @@ bool retro_load_game(const struct retro_game_info *info)
if (engine_program_cb && engine_program_cb->load_game)
engine_program_cb->load_game(info);
vr_active = false;
if (vr_user_enable && engine_program_cb == &engine_program_modelviewer &&
modelviewer_vr_capable())
vr_active = vr_request_session();
return true;
}
@@ -554,6 +619,7 @@ void retro_unload_game(void)
if (convert_buffer)
delete[] convert_buffer;
convert_buffer = NULL;
vr_active = false;
}
unsigned retro_get_region(void)
+9
View File
@@ -22,6 +22,8 @@
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "vr_support.hpp"
extern retro_input_poll_t input_poll_cb;
extern retro_input_state_t input_state_cb;
extern struct retro_hw_render_callback hw_render;
@@ -59,4 +61,11 @@ typedef struct engine_program
extern const engine_program_t engine_program_instancingviewer;
extern const engine_program_t engine_program_modelviewer;
// VR
extern bool vr_active;
extern unsigned vr_eye_width;
extern unsigned vr_eye_height;
extern bool modelviewer_vr_capable(void);
extern bool vr_poll_frame(struct retro_vr_frame_state *fs);
#endif
+95 -12
View File
@@ -59,12 +59,19 @@ enum
unsigned mode_engine = MODE_MODELVIEWER;
static bool scene_flip_y(void) { return !hw_render.bottom_left_origin; }
static struct retro_vr_frame_state vr_frame;
bool modelviewer_vr_capable(void) { return mode_engine == MODE_SCENEWALKER; }
static float player_view_deg_x;
static float player_view_deg_y;
static vec3 player_pos(0, 2, 0);
extern bool vr_user_enable;
static vec3 scenewalker_check_input(void)
{
static float player_view_deg_x;
static float player_view_deg_y;
static vec3 player_pos(0, 2, 0);
input_poll_cb();
int analog_x = input_state_cb(0, RETRO_DEVICE_ANALOG,
@@ -152,6 +159,9 @@ static vec3 scenewalker_check_input(void)
}
#endif
if (vr_active)
analog_ry = 0; /* head controls pitch */
player_view_deg_y += analog_rx * -0.00008f;
player_view_deg_x += analog_ry * -0.00005f;
@@ -166,6 +176,13 @@ static vec3 scenewalker_check_input(void)
vec3 right_walk_dir = vec3(rotate_y_right * vec4(0, 0, -1, 1));
vec3 front_walk_dir = vec3(rotate_y * vec4(0, 0, -1, 1));
if (vr_active)
{
/* walk where the head points, on the ground plane */
front_walk_dir = vec3(rotate_y * vec4(vr_head_forward_xz(vr_frame), 0.0f));
right_walk_dir = cross(front_walk_dir, vec3(0, 1, 0));
}
vec3 velocity = front_walk_dir * vec3(analog_y * -0.000002f) +
right_walk_dir * vec3(analog_x * 0.000002f);
@@ -539,9 +556,15 @@ static void init_mesh(const std::string& path)
mat4 projection;
if (mode_engine == MODE_SCENEWALKER)
projection = scale(mat4(1.0), vec3(1, -1, 1)) * perspective(45.0f, 640.0f / 480.0f, 1.0f, 100.0f);
{
projection = perspective(45.0f, 640.0f / 480.0f, 1.0f, 100.0f);
if (scene_flip_y())
projection = scale(mat4(1.0), vec3(1, -1, 1)) * projection;
}
else
{
projection = scale(mat4(1.0), vec3(1, -1, 1)) * perspective(45.0f, 4.0f / 3.0f, 0.2f, 100.0f);
}
for (unsigned i = 0; i < meshes.size(); i++)
{
@@ -617,34 +640,92 @@ static void modelviewer_update_variables(retro_environment_t environ_cb)
if (!first_init)
modelviewer_context_reset();
}
var.key = "3dengine-modelviewer-vr-enable";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
vr_user_enable = false;
else if (!strcmp(var.value, "enabled"))
vr_user_enable = true;
if (!first_init)
modelviewer_context_reset();
}
}
static void modelviewer_run(void)
{
unsigned i;
unsigned i, width, height;
const bool scene = (mode_engine == MODE_SCENEWALKER);
bool vr = false;
if (scene && vr_active)
{
vr_poll_frame(&vr_frame); /* once, before input, per the API doc */
vr = vr_active; /* may drop if the resize renegotiation failed */
}
vec3 look_dir = modelviewer_check_input();
(void)look_dir;
width = vr ? vr_eye_width * 2 : engine_width;
height = vr ? vr_eye_height : engine_height;
glBindFramebuffer(GL_FRAMEBUFFER, hw_render.get_current_framebuffer());
glViewport(0, 0, engine_width, engine_height);
glViewport(0, 0, width, height);
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glFrontFace(GL_CW); // When we flip vertically, orientation changes.
/* Only the flipped projection needs CW. Unflipped is normal CCW. */
glFrontFace((scene && !scene_flip_y()) ? GL_CCW : GL_CW);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
for (i = 0; i < meshes.size(); i++)
meshes[i]->render();
if (vr)
{
unsigned eye;
mat4 rig = translate(mat4(1.0), player_pos) *
rotate(mat4(1.0), player_view_deg_y, vec3(0, 1, 0));
glEnable(GL_SCISSOR_TEST);
for (eye = 0; eye < 2; eye++)
{
const struct retro_vr_eye_state &es = vr_frame.eyes[eye];
mat4 eye_world = rig * vr_eye_pose_matrix(es);
mat4 view = inverse(eye_world);
mat4 proj = vr_projection(es.fov_tan, 0.1f, 100.0f);
vec3 eye_pos = vec3(eye_world[3]);
if (scene_flip_y())
proj = scale(mat4(1.0), vec3(1, -1, 1)) * proj;
glViewport(eye * vr_eye_width, 0, vr_eye_width, vr_eye_height);
glScissor (eye * vr_eye_width, 0, vr_eye_width, vr_eye_height);
for (i = 0; i < meshes.size(); i++)
{
meshes[i]->set_projection(proj);
meshes[i]->set_view(view);
meshes[i]->set_eye(eye_pos);
meshes[i]->render();
}
}
glDisable(GL_SCISSOR_TEST);
}
else
for (i = 0; i < meshes.size(); i++)
meshes[i]->render();
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
video_cb(RETRO_HW_FRAME_BUFFER_VALID, engine_width, engine_height, 0);
video_cb(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0);
}
static void scenewalker_reset_mesh_path(void)
{
//always assume matching .obj file is there
@@ -677,6 +758,8 @@ static void modelviewer_load_game(const struct retro_game_info *info)
player_size = vec3(0, 0, 0);
first_init = false;
vr_frame_defaults(&vr_frame);
}
const engine_program_t engine_program_modelviewer = {
+73
View File
@@ -0,0 +1,73 @@
/*
* Libretro 3DEngine - VR helpers
*
*/
#ifndef VR_SUPPORT_HPP__
#define VR_SUPPORT_HPP__
#include <math.h>
#include <string.h>
#include "libretro.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/quaternion.hpp"
/* Identity pose + 90 degree symmetric FOV. Used until the frontend gives us
* a valid sample (first frame), so we never normalize a zero quaternion. */
static inline void vr_frame_defaults(struct retro_vr_frame_state *fs)
{
unsigned i, j;
memset(fs, 0, sizeof(*fs));
for (i = 0; i < 2; i++)
{
fs->eyes[i].orientation[3] = 1.0f;
for (j = 0; j < 4; j++)
fs->eyes[i].fov_tan[j] = 1.0f;
}
}
static inline glm::quat vr_quat(const float q[4])
{
/* libretro: (x, y, z, w). glm::quat constructor: (w, x, y, z). */
return glm::normalize(glm::quat(q[3], q[0], q[1], q[2]));
}
/* Eye -> tracking space transform. View matrix is the inverse of this
* (after applying the rig transform). */
static inline glm::mat4 vr_eye_pose_matrix(const struct retro_vr_eye_state &e)
{
glm::mat4 m = glm::mat4_cast(vr_quat(e.orientation));
m[3] = glm::vec4(e.position[0], e.position[1], e.position[2], 1.0f);
return m;
}
/* Asymmetric frustum from the four tangents. fabsf() so it works whether the
* frontend reports magnitudes or OpenXR-style signed angles
* (left/down negative) - see notes on fov_tan in the reply. */
static inline glm::mat4 vr_projection(const float t[4], float znear, float zfar)
{
float l = fabsf(t[0]) * znear;
float r = fabsf(t[1]) * znear;
float u = fabsf(t[2]) * znear;
float d = fabsf(t[3]) * znear;
return glm::frustum(-l, r, -d, u, znear, zfar);
}
/* Unit vector, in tracking space, of where the head is facing, flattened onto
* the ground plane. Averages both eyes (some HMDs cant their displays). */
static inline glm::vec3 vr_head_forward_xz(const struct retro_vr_frame_state &fs)
{
unsigned i;
glm::vec3 f(0.0f);
for (i = 0; i < 2; i++)
f += vr_quat(fs.eyes[i].orientation) * glm::vec3(0.0f, 0.0f, -1.0f);
f.y = 0.0f;
if (glm::length(f) < 0.001f) /* looking straight up/down */
return glm::vec3(0.0f, 0.0f, -1.0f);
return glm::normalize(f);
}
#endif