00cc9309cb
de6e324bdseparate emu thread10d3daf86Roms List improvements95d202f37Let's make the rom list process on a separate thread so the emulator doesnt take ages to load.fc306967fWow the ROM Header was just completely busted. Game list view works nowbad1691eefuck this shit2b59e5f46game list in progressd26417b83remappable inputs in progressac4af8106inpute72abc240update readme430139dc9Qt6 frontend3080d4d45Fix this small bug too08cd13b85Cop0 unused functions do not actually pose a threat (as per manual). They don't do anything, so shall we.61bb4fb44make idle loop detection a little more specific with where the load goesb037de4c3SAZDFsdff12e81e73eneed to figure out why n64-systemtest loops indefinitely at some address that appears to be valid (i think it's me not invalidating the cache properly)204f0e13bidle skipping seems to work!cb8bb634asdkfjlasdf58e5c89c1Fix compilation issue on my machine (no idea)24fb2898eattempting more serious idle skipping214719577Place rsp.Step inside cached interpreter. Gains about 3 more fpsbb97dcc23mmmmm920b77d38wjkhasdfjhkasdf430ccdab4it's a start...4f42a673aCached interpreter plays Mario 64. Start looking into RSP as wellc9a030787idle skipping works!5fbda03cenew idea366637abaIdle skipping... maybe?609fa2fb0Cache instructions implemented but broken lmao. Commented out for nowe140a6d12- Stop using inheritance for CPU, instead use composition. - Introduce KAIZEN_JIT_ENABLED optional define instead of relying on __aarch64__ and the like. - More cache work68e613057prep cache impl811b4d809fix clang formatfda755f7didkd5024ebbfsmall MI refactor in preparation of (eventually) implementing the RDRAM interface properly694b45341Merge commit '206dcdedf195fb320913584180edb12c7731e396' as 'external/SDL'206dcdedfSquashed 'external/SDL/' content from commit 4d17b99d0a4d16e1cb4need to update sdl848b19920Fix compilation errordb61b5299Merge commit 'e94a94559f28e49678fbcf72199a5258137b0fe9' as 'external/imgui'e94a94559Squashed 'external/imgui/' content from commit 02e9b8cac52edb3757need to update imguic1a705e86Emulate weird JALR behaviour4b4c32f4bFix exception for "unusable COP1" in 4 instructions i missed accidentally (again)df5828142Bug putting 0s in the log everywheref8b580048Make isviewer a sink to file8241e9735Fix exception for "unusable COP1" in 4 instructions i missed accidentallyb29715f20small changesd9a620bc1make use of my new small utility library0d1aa938eAdd 'external/ircolib/' from commit 'ce3cd726c8df8388d554abf8bb55d55020eb4450'e64eb40b3Fuck git git-subtree-dir: external/ircolib git-subtree-split:de6e324bde
984 lines
37 KiB
C
984 lines
37 KiB
C
/*
|
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely.
|
|
*/
|
|
|
|
/*
|
|
* testgpu_spinning_cube_xr.c - SDL3 GPU API OpenXR Spinning Cubes Test
|
|
*
|
|
* This is an XR-enabled version of testgpu_spinning_cube that renders
|
|
* spinning colored cubes in VR using OpenXR and SDL's GPU API.
|
|
*
|
|
* Rendering approach: Multi-pass stereo (one render pass per eye)
|
|
* This is the simplest and most compatible approach, working on all
|
|
* OpenXR-capable platforms (Desktop VR runtimes, Quest, etc.)
|
|
*
|
|
* For more information on stereo rendering techniques, see:
|
|
* - Multi-pass: Traditional, 2 render passes (used here)
|
|
* - Multiview (GL_OVR_multiview): Single pass with texture arrays
|
|
* - Single-pass instanced: GPU instancing to select eye
|
|
*/
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3/SDL_main.h>
|
|
|
|
/* Include OpenXR headers BEFORE SDL_openxr.h to get full type definitions */
|
|
#ifdef HAVE_OPENXR_H
|
|
#include <openxr/openxr.h>
|
|
#else
|
|
/* SDL includes a copy for building on systems without the OpenXR SDK */
|
|
#include "../src/video/khronos/openxr/openxr.h"
|
|
#endif
|
|
|
|
#include <SDL3/SDL_openxr.h>
|
|
|
|
/* Standard library for exit() */
|
|
#include <stdlib.h>
|
|
|
|
/* Include compiled shader bytecode for all backends */
|
|
#include "testgpu/cube.frag.dxil.h"
|
|
#include "testgpu/cube.frag.spv.h"
|
|
#include "testgpu/cube.vert.dxil.h"
|
|
#include "testgpu/cube.vert.spv.h"
|
|
|
|
#define CHECK_CREATE(var, thing) { if (!(var)) { SDL_Log("Failed to create %s: %s", thing, SDL_GetError()); return false; } }
|
|
#define XR_CHECK(result, msg) do { if (XR_FAILED(result)) { SDL_Log("OpenXR Error: %s (result=%d)", msg, (int)(result)); return false; } } while(0)
|
|
#define XR_CHECK_QUIT(result, msg) do { if (XR_FAILED(result)) { SDL_Log("OpenXR Error: %s (result=%d)", msg, (int)(result)); quit(2); return; } } while(0)
|
|
|
|
/* ========================================================================
|
|
* Math Types and Functions
|
|
* ======================================================================== */
|
|
|
|
typedef struct { float x, y, z; } Vec3;
|
|
typedef struct { float m[16]; } Mat4;
|
|
|
|
static Mat4 Mat4_Multiply(Mat4 a, Mat4 b)
|
|
{
|
|
Mat4 result = {{0}};
|
|
for (int i = 0; i < 4; i++) {
|
|
for (int j = 0; j < 4; j++) {
|
|
for (int k = 0; k < 4; k++) {
|
|
result.m[i * 4 + j] += a.m[i * 4 + k] * b.m[k * 4 + j];
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Mat4 Mat4_Translation(float x, float y, float z)
|
|
{
|
|
return (Mat4){{ 1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,z,1 }};
|
|
}
|
|
|
|
static Mat4 Mat4_Scale(float s)
|
|
{
|
|
return (Mat4){{ s,0,0,0, 0,s,0,0, 0,0,s,0, 0,0,0,1 }};
|
|
}
|
|
|
|
static Mat4 Mat4_RotationY(float rad)
|
|
{
|
|
float c = SDL_cosf(rad), s = SDL_sinf(rad);
|
|
return (Mat4){{ c,0,-s,0, 0,1,0,0, s,0,c,0, 0,0,0,1 }};
|
|
}
|
|
|
|
static Mat4 Mat4_RotationX(float rad)
|
|
{
|
|
float c = SDL_cosf(rad), s = SDL_sinf(rad);
|
|
return (Mat4){{ 1,0,0,0, 0,c,s,0, 0,-s,c,0, 0,0,0,1 }};
|
|
}
|
|
|
|
/* Convert XrPosef to view matrix (inverted transform) */
|
|
static Mat4 Mat4_FromXrPose(XrPosef pose)
|
|
{
|
|
float x = pose.orientation.x, y = pose.orientation.y;
|
|
float z = pose.orientation.z, w = pose.orientation.w;
|
|
|
|
/* Quaternion to rotation matrix columns */
|
|
Vec3 right = { 1-2*(y*y+z*z), 2*(x*y+w*z), 2*(x*z-w*y) };
|
|
Vec3 up = { 2*(x*y-w*z), 1-2*(x*x+z*z), 2*(y*z+w*x) };
|
|
Vec3 fwd = { 2*(x*z+w*y), 2*(y*z-w*x), 1-2*(x*x+y*y) };
|
|
Vec3 pos = { pose.position.x, pose.position.y, pose.position.z };
|
|
|
|
/* Inverted transform for view matrix */
|
|
float dr = -(right.x*pos.x + right.y*pos.y + right.z*pos.z);
|
|
float du = -(up.x*pos.x + up.y*pos.y + up.z*pos.z);
|
|
float df = -(fwd.x*pos.x + fwd.y*pos.y + fwd.z*pos.z);
|
|
|
|
return (Mat4){{ right.x,up.x,fwd.x,0, right.y,up.y,fwd.y,0, right.z,up.z,fwd.z,0, dr,du,df,1 }};
|
|
}
|
|
|
|
/* Create asymmetric projection matrix from XR FOV */
|
|
static Mat4 Mat4_Projection(XrFovf fov, float nearZ, float farZ)
|
|
{
|
|
float tL = SDL_tanf(fov.angleLeft), tR = SDL_tanf(fov.angleRight);
|
|
float tU = SDL_tanf(fov.angleUp), tD = SDL_tanf(fov.angleDown);
|
|
float w = tR - tL, h = tU - tD;
|
|
|
|
return (Mat4){{
|
|
2/w, 0, 0, 0,
|
|
0, 2/h, 0, 0,
|
|
(tR+tL)/w, (tU+tD)/h, -farZ/(farZ-nearZ), -1,
|
|
0, 0, -(farZ*nearZ)/(farZ-nearZ), 0
|
|
}};
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Vertex Data
|
|
* ======================================================================== */
|
|
|
|
typedef struct {
|
|
float x, y, z;
|
|
Uint8 r, g, b, a;
|
|
} PositionColorVertex;
|
|
|
|
/* Cube vertices - 0.25m half-size, each face a different color */
|
|
static const float CUBE_HALF_SIZE = 0.25f;
|
|
|
|
/* ========================================================================
|
|
* OpenXR Function Pointers (loaded dynamically)
|
|
* ======================================================================== */
|
|
|
|
static PFN_xrGetInstanceProcAddr pfn_xrGetInstanceProcAddr = NULL;
|
|
static PFN_xrEnumerateViewConfigurationViews pfn_xrEnumerateViewConfigurationViews = NULL;
|
|
static PFN_xrEnumerateSwapchainImages pfn_xrEnumerateSwapchainImages = NULL;
|
|
static PFN_xrCreateReferenceSpace pfn_xrCreateReferenceSpace = NULL;
|
|
static PFN_xrDestroySpace pfn_xrDestroySpace = NULL;
|
|
static PFN_xrDestroySession pfn_xrDestroySession = NULL;
|
|
static PFN_xrDestroyInstance pfn_xrDestroyInstance = NULL;
|
|
static PFN_xrPollEvent pfn_xrPollEvent = NULL;
|
|
static PFN_xrBeginSession pfn_xrBeginSession = NULL;
|
|
static PFN_xrEndSession pfn_xrEndSession = NULL;
|
|
static PFN_xrWaitFrame pfn_xrWaitFrame = NULL;
|
|
static PFN_xrBeginFrame pfn_xrBeginFrame = NULL;
|
|
static PFN_xrEndFrame pfn_xrEndFrame = NULL;
|
|
static PFN_xrLocateViews pfn_xrLocateViews = NULL;
|
|
static PFN_xrAcquireSwapchainImage pfn_xrAcquireSwapchainImage = NULL;
|
|
static PFN_xrWaitSwapchainImage pfn_xrWaitSwapchainImage = NULL;
|
|
static PFN_xrReleaseSwapchainImage pfn_xrReleaseSwapchainImage = NULL;
|
|
|
|
/* ========================================================================
|
|
* Global State
|
|
* ======================================================================== */
|
|
|
|
/* OpenXR state */
|
|
static XrInstance xr_instance = XR_NULL_HANDLE;
|
|
static XrSystemId xr_system_id = XR_NULL_SYSTEM_ID;
|
|
static XrSession xr_session = XR_NULL_HANDLE;
|
|
static XrSpace xr_local_space = XR_NULL_HANDLE;
|
|
static bool xr_session_running = false;
|
|
static bool xr_should_quit = false;
|
|
|
|
/* Swapchain state */
|
|
typedef struct {
|
|
XrSwapchain swapchain;
|
|
SDL_GPUTexture **images;
|
|
SDL_GPUTexture *depth_texture; /* Local depth buffer for z-ordering */
|
|
XrExtent2Di size;
|
|
SDL_GPUTextureFormat format;
|
|
Uint32 image_count;
|
|
} VRSwapchain;
|
|
|
|
/* Depth buffer format - use D24 for wide compatibility */
|
|
static const SDL_GPUTextureFormat DEPTH_FORMAT = SDL_GPU_TEXTUREFORMAT_D24_UNORM;
|
|
|
|
static VRSwapchain *vr_swapchains = NULL;
|
|
static XrView *xr_views = NULL;
|
|
static Uint32 view_count = 0;
|
|
|
|
/* SDL GPU state */
|
|
static SDL_GPUDevice *gpu_device = NULL;
|
|
static SDL_GPUGraphicsPipeline *pipeline = NULL;
|
|
static SDL_GPUBuffer *vertex_buffer = NULL;
|
|
static SDL_GPUBuffer *index_buffer = NULL;
|
|
|
|
/* Animation time */
|
|
static float anim_time = 0.0f;
|
|
static Uint64 last_ticks = 0;
|
|
|
|
/* Cube scene configuration */
|
|
#define NUM_CUBES 5
|
|
static Vec3 cube_positions[NUM_CUBES] = {
|
|
{ 0.0f, 0.0f, -2.0f }, /* Center, in front */
|
|
{ -1.2f, 0.4f, -2.5f }, /* Upper left */
|
|
{ 1.2f, 0.3f, -2.5f }, /* Upper right */
|
|
{ -0.6f, -0.4f, -1.8f }, /* Lower left close */
|
|
{ 0.6f, -0.3f, -1.8f }, /* Lower right close */
|
|
};
|
|
static float cube_scales[NUM_CUBES] = { 1.0f, 0.6f, 0.6f, 0.5f, 0.5f };
|
|
static float cube_speeds[NUM_CUBES] = { 1.0f, 1.5f, -1.2f, 2.0f, -0.8f };
|
|
|
|
/* ========================================================================
|
|
* Cleanup and Quit
|
|
* ======================================================================== */
|
|
|
|
static void quit(int rc)
|
|
{
|
|
SDL_Log("Cleaning up...");
|
|
|
|
/* CRITICAL: Wait for GPU to finish before destroying resources
|
|
* Per PR #14837 discussion - prevents Vulkan validation errors */
|
|
if (gpu_device) {
|
|
SDL_WaitForGPUIdle(gpu_device);
|
|
}
|
|
|
|
/* Release GPU resources first */
|
|
if (pipeline) {
|
|
SDL_ReleaseGPUGraphicsPipeline(gpu_device, pipeline);
|
|
pipeline = NULL;
|
|
}
|
|
if (vertex_buffer) {
|
|
SDL_ReleaseGPUBuffer(gpu_device, vertex_buffer);
|
|
vertex_buffer = NULL;
|
|
}
|
|
if (index_buffer) {
|
|
SDL_ReleaseGPUBuffer(gpu_device, index_buffer);
|
|
index_buffer = NULL;
|
|
}
|
|
|
|
/* Release swapchains and depth textures */
|
|
if (vr_swapchains) {
|
|
for (Uint32 i = 0; i < view_count; i++) {
|
|
if (vr_swapchains[i].depth_texture) {
|
|
SDL_ReleaseGPUTexture(gpu_device, vr_swapchains[i].depth_texture);
|
|
}
|
|
if (vr_swapchains[i].swapchain) {
|
|
SDL_DestroyGPUXRSwapchain(gpu_device, vr_swapchains[i].swapchain, vr_swapchains[i].images);
|
|
}
|
|
}
|
|
SDL_free(vr_swapchains);
|
|
vr_swapchains = NULL;
|
|
}
|
|
|
|
if (xr_views) {
|
|
SDL_free(xr_views);
|
|
xr_views = NULL;
|
|
}
|
|
|
|
/* Destroy OpenXR resources */
|
|
if (xr_local_space && pfn_xrDestroySpace) {
|
|
pfn_xrDestroySpace(xr_local_space);
|
|
xr_local_space = XR_NULL_HANDLE;
|
|
}
|
|
if (xr_session && pfn_xrDestroySession) {
|
|
pfn_xrDestroySession(xr_session);
|
|
xr_session = XR_NULL_HANDLE;
|
|
}
|
|
|
|
/* Destroy GPU device (this also handles XR instance cleanup) */
|
|
if (gpu_device) {
|
|
SDL_DestroyGPUDevice(gpu_device);
|
|
gpu_device = NULL;
|
|
}
|
|
|
|
SDL_Quit();
|
|
exit(rc);
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Shader Loading
|
|
* ======================================================================== */
|
|
|
|
static SDL_GPUShader *load_shader(bool is_vertex, Uint32 sampler_count, Uint32 uniform_buffer_count)
|
|
{
|
|
SDL_GPUShaderCreateInfo createinfo;
|
|
createinfo.num_samplers = sampler_count;
|
|
createinfo.num_storage_buffers = 0;
|
|
createinfo.num_storage_textures = 0;
|
|
createinfo.num_uniform_buffers = uniform_buffer_count;
|
|
|
|
SDL_GPUShaderFormat format = SDL_GetGPUShaderFormats(gpu_device);
|
|
if (format & SDL_GPU_SHADERFORMAT_DXIL) {
|
|
createinfo.format = SDL_GPU_SHADERFORMAT_DXIL;
|
|
if (is_vertex) {
|
|
createinfo.code = cube_vert_dxil;
|
|
createinfo.code_size = cube_vert_dxil_len;
|
|
createinfo.entrypoint = "main";
|
|
} else {
|
|
createinfo.code = cube_frag_dxil;
|
|
createinfo.code_size = cube_frag_dxil_len;
|
|
createinfo.entrypoint = "main";
|
|
}
|
|
} else if (format & SDL_GPU_SHADERFORMAT_SPIRV) {
|
|
createinfo.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
|
if (is_vertex) {
|
|
createinfo.code = cube_vert_spv;
|
|
createinfo.code_size = cube_vert_spv_len;
|
|
createinfo.entrypoint = "main";
|
|
} else {
|
|
createinfo.code = cube_frag_spv;
|
|
createinfo.code_size = cube_frag_spv_len;
|
|
createinfo.entrypoint = "main";
|
|
}
|
|
} else {
|
|
SDL_Log("No supported shader format found!");
|
|
return NULL;
|
|
}
|
|
|
|
createinfo.stage = is_vertex ? SDL_GPU_SHADERSTAGE_VERTEX : SDL_GPU_SHADERSTAGE_FRAGMENT;
|
|
createinfo.props = 0;
|
|
|
|
return SDL_CreateGPUShader(gpu_device, &createinfo);
|
|
}
|
|
|
|
/* ========================================================================
|
|
* OpenXR Function Loading
|
|
* ======================================================================== */
|
|
|
|
static bool load_xr_functions(void)
|
|
{
|
|
pfn_xrGetInstanceProcAddr = (PFN_xrGetInstanceProcAddr)SDL_OpenXR_GetXrGetInstanceProcAddr();
|
|
if (!pfn_xrGetInstanceProcAddr) {
|
|
SDL_Log("Failed to get xrGetInstanceProcAddr");
|
|
return false;
|
|
}
|
|
|
|
#define XR_LOAD(fn) \
|
|
if (XR_FAILED(pfn_xrGetInstanceProcAddr(xr_instance, #fn, (PFN_xrVoidFunction*)&pfn_##fn))) { \
|
|
SDL_Log("Failed to load " #fn); \
|
|
return false; \
|
|
}
|
|
|
|
XR_LOAD(xrEnumerateViewConfigurationViews);
|
|
XR_LOAD(xrEnumerateSwapchainImages);
|
|
XR_LOAD(xrCreateReferenceSpace);
|
|
XR_LOAD(xrDestroySpace);
|
|
XR_LOAD(xrDestroySession);
|
|
XR_LOAD(xrDestroyInstance);
|
|
XR_LOAD(xrPollEvent);
|
|
XR_LOAD(xrBeginSession);
|
|
XR_LOAD(xrEndSession);
|
|
XR_LOAD(xrWaitFrame);
|
|
XR_LOAD(xrBeginFrame);
|
|
XR_LOAD(xrEndFrame);
|
|
XR_LOAD(xrLocateViews);
|
|
XR_LOAD(xrAcquireSwapchainImage);
|
|
XR_LOAD(xrWaitSwapchainImage);
|
|
XR_LOAD(xrReleaseSwapchainImage);
|
|
|
|
#undef XR_LOAD
|
|
|
|
SDL_Log("Loaded all XR functions successfully");
|
|
return true;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Pipeline and Buffer Creation
|
|
* ======================================================================== */
|
|
|
|
static bool create_pipeline(SDL_GPUTextureFormat color_format)
|
|
{
|
|
SDL_GPUShader *vert_shader = load_shader(true, 0, 1);
|
|
SDL_GPUShader *frag_shader = load_shader(false, 0, 0);
|
|
|
|
if (!vert_shader || !frag_shader) {
|
|
if (vert_shader) SDL_ReleaseGPUShader(gpu_device, vert_shader);
|
|
if (frag_shader) SDL_ReleaseGPUShader(gpu_device, frag_shader);
|
|
return false;
|
|
}
|
|
|
|
SDL_GPUGraphicsPipelineCreateInfo pipeline_info = {
|
|
.vertex_shader = vert_shader,
|
|
.fragment_shader = frag_shader,
|
|
.target_info = {
|
|
.num_color_targets = 1,
|
|
.color_target_descriptions = (SDL_GPUColorTargetDescription[]){{
|
|
.format = color_format
|
|
}},
|
|
.has_depth_stencil_target = true,
|
|
.depth_stencil_format = DEPTH_FORMAT
|
|
},
|
|
.depth_stencil_state = {
|
|
.enable_depth_test = true,
|
|
.enable_depth_write = true,
|
|
.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL
|
|
},
|
|
.rasterizer_state = {
|
|
.cull_mode = SDL_GPU_CULLMODE_BACK,
|
|
.front_face = SDL_GPU_FRONTFACE_CLOCKWISE, /* Cube indices wind clockwise when viewed from outside */
|
|
.fill_mode = SDL_GPU_FILLMODE_FILL
|
|
},
|
|
.vertex_input_state = {
|
|
.num_vertex_buffers = 1,
|
|
.vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){{
|
|
.slot = 0,
|
|
.pitch = sizeof(PositionColorVertex),
|
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
|
}},
|
|
.num_vertex_attributes = 2,
|
|
.vertex_attributes = (SDL_GPUVertexAttribute[]){{
|
|
.location = 0,
|
|
.buffer_slot = 0,
|
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
|
.offset = 0
|
|
}, {
|
|
.location = 1,
|
|
.buffer_slot = 0,
|
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM,
|
|
.offset = sizeof(float) * 3
|
|
}}
|
|
},
|
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST
|
|
};
|
|
|
|
pipeline = SDL_CreateGPUGraphicsPipeline(gpu_device, &pipeline_info);
|
|
|
|
SDL_ReleaseGPUShader(gpu_device, vert_shader);
|
|
SDL_ReleaseGPUShader(gpu_device, frag_shader);
|
|
|
|
if (!pipeline) {
|
|
SDL_Log("Failed to create pipeline: %s", SDL_GetError());
|
|
return false;
|
|
}
|
|
|
|
SDL_Log("Created graphics pipeline for format %d", color_format);
|
|
return true;
|
|
}
|
|
|
|
static bool create_cube_buffers(void)
|
|
{
|
|
float s = CUBE_HALF_SIZE;
|
|
|
|
PositionColorVertex vertices[24] = {
|
|
/* Front face (red) */
|
|
{-s,-s,-s, 255,0,0,255}, {s,-s,-s, 255,0,0,255}, {s,s,-s, 255,0,0,255}, {-s,s,-s, 255,0,0,255},
|
|
/* Back face (green) */
|
|
{s,-s,s, 0,255,0,255}, {-s,-s,s, 0,255,0,255}, {-s,s,s, 0,255,0,255}, {s,s,s, 0,255,0,255},
|
|
/* Left face (blue) */
|
|
{-s,-s,s, 0,0,255,255}, {-s,-s,-s, 0,0,255,255}, {-s,s,-s, 0,0,255,255}, {-s,s,s, 0,0,255,255},
|
|
/* Right face (yellow) */
|
|
{s,-s,-s, 255,255,0,255}, {s,-s,s, 255,255,0,255}, {s,s,s, 255,255,0,255}, {s,s,-s, 255,255,0,255},
|
|
/* Top face (magenta) */
|
|
{-s,s,-s, 255,0,255,255}, {s,s,-s, 255,0,255,255}, {s,s,s, 255,0,255,255}, {-s,s,s, 255,0,255,255},
|
|
/* Bottom face (cyan) */
|
|
{-s,-s,s, 0,255,255,255}, {s,-s,s, 0,255,255,255}, {s,-s,-s, 0,255,255,255}, {-s,-s,-s, 0,255,255,255}
|
|
};
|
|
|
|
Uint16 indices[36] = {
|
|
0,1,2, 0,2,3, /* Front */
|
|
4,5,6, 4,6,7, /* Back */
|
|
8,9,10, 8,10,11, /* Left */
|
|
12,13,14, 12,14,15, /* Right */
|
|
16,17,18, 16,18,19, /* Top */
|
|
20,21,22, 20,22,23 /* Bottom */
|
|
};
|
|
|
|
SDL_GPUBufferCreateInfo vertex_buf_info = {
|
|
.usage = SDL_GPU_BUFFERUSAGE_VERTEX,
|
|
.size = sizeof(vertices)
|
|
};
|
|
vertex_buffer = SDL_CreateGPUBuffer(gpu_device, &vertex_buf_info);
|
|
CHECK_CREATE(vertex_buffer, "Vertex Buffer");
|
|
|
|
SDL_GPUBufferCreateInfo index_buf_info = {
|
|
.usage = SDL_GPU_BUFFERUSAGE_INDEX,
|
|
.size = sizeof(indices)
|
|
};
|
|
index_buffer = SDL_CreateGPUBuffer(gpu_device, &index_buf_info);
|
|
CHECK_CREATE(index_buffer, "Index Buffer");
|
|
|
|
/* Create transfer buffer and upload data */
|
|
SDL_GPUTransferBufferCreateInfo transfer_info = {
|
|
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
|
.size = sizeof(vertices) + sizeof(indices)
|
|
};
|
|
SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(gpu_device, &transfer_info);
|
|
CHECK_CREATE(transfer, "Transfer Buffer");
|
|
|
|
void *data = SDL_MapGPUTransferBuffer(gpu_device, transfer, false);
|
|
SDL_memcpy(data, vertices, sizeof(vertices));
|
|
SDL_memcpy((Uint8*)data + sizeof(vertices), indices, sizeof(indices));
|
|
SDL_UnmapGPUTransferBuffer(gpu_device, transfer);
|
|
|
|
SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(gpu_device);
|
|
SDL_GPUCopyPass *copy_pass = SDL_BeginGPUCopyPass(cmd);
|
|
|
|
SDL_GPUTransferBufferLocation src_vertex = { .transfer_buffer = transfer, .offset = 0 };
|
|
SDL_GPUBufferRegion dst_vertex = { .buffer = vertex_buffer, .offset = 0, .size = sizeof(vertices) };
|
|
SDL_UploadToGPUBuffer(copy_pass, &src_vertex, &dst_vertex, false);
|
|
|
|
SDL_GPUTransferBufferLocation src_index = { .transfer_buffer = transfer, .offset = sizeof(vertices) };
|
|
SDL_GPUBufferRegion dst_index = { .buffer = index_buffer, .offset = 0, .size = sizeof(indices) };
|
|
SDL_UploadToGPUBuffer(copy_pass, &src_index, &dst_index, false);
|
|
|
|
SDL_EndGPUCopyPass(copy_pass);
|
|
SDL_SubmitGPUCommandBuffer(cmd);
|
|
SDL_ReleaseGPUTransferBuffer(gpu_device, transfer);
|
|
|
|
SDL_Log("Created cube vertex (%u bytes) and index (%u bytes) buffers", (unsigned int)sizeof(vertices), (unsigned int)sizeof(indices));
|
|
return true;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* XR Session Initialization
|
|
* ======================================================================== */
|
|
|
|
static bool init_xr_session(void)
|
|
{
|
|
XrResult result;
|
|
|
|
/* Create session */
|
|
XrSessionCreateInfo session_info = { XR_TYPE_SESSION_CREATE_INFO };
|
|
result = SDL_CreateGPUXRSession(gpu_device, &session_info, &xr_session);
|
|
XR_CHECK(result, "Failed to create XR session");
|
|
|
|
/* Create reference space */
|
|
XrReferenceSpaceCreateInfo space_info = { XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
|
|
space_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
|
space_info.poseInReferenceSpace.orientation.w = 1.0f; /* Identity quaternion */
|
|
|
|
result = pfn_xrCreateReferenceSpace(xr_session, &space_info, &xr_local_space);
|
|
XR_CHECK(result, "Failed to create reference space");
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool create_swapchains(void)
|
|
{
|
|
XrResult result;
|
|
|
|
/* Get view configuration */
|
|
result = pfn_xrEnumerateViewConfigurationViews(
|
|
xr_instance, xr_system_id,
|
|
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
|
|
0, &view_count, NULL);
|
|
XR_CHECK(result, "Failed to enumerate view config views (count)");
|
|
|
|
SDL_Log("View count: %" SDL_PRIu32, view_count);
|
|
|
|
XrViewConfigurationView *view_configs = SDL_calloc(view_count, sizeof(XrViewConfigurationView));
|
|
for (Uint32 i = 0; i < view_count; i++) {
|
|
view_configs[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
|
|
}
|
|
|
|
result = pfn_xrEnumerateViewConfigurationViews(
|
|
xr_instance, xr_system_id,
|
|
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
|
|
view_count, &view_count, view_configs);
|
|
if (XR_FAILED(result)) {
|
|
SDL_free(view_configs);
|
|
SDL_Log("Failed to enumerate view config views");
|
|
return false;
|
|
}
|
|
|
|
/* Allocate swapchains and views */
|
|
vr_swapchains = SDL_calloc(view_count, sizeof(VRSwapchain));
|
|
xr_views = SDL_calloc(view_count, sizeof(XrView));
|
|
|
|
/* Query available swapchain formats
|
|
* Per PR #14837: format arrays are terminated with SDL_GPU_TEXTUREFORMAT_INVALID */
|
|
int num_formats = 0;
|
|
SDL_GPUTextureFormat *formats = SDL_GetGPUXRSwapchainFormats(gpu_device, xr_session, &num_formats);
|
|
if (!formats || num_formats == 0) {
|
|
SDL_Log("Failed to get XR swapchain formats");
|
|
SDL_free(view_configs);
|
|
return false;
|
|
}
|
|
|
|
/* Use first available format (typically sRGB)
|
|
* Note: Could iterate with: while (formats[i] != SDL_GPU_TEXTUREFORMAT_INVALID) */
|
|
SDL_GPUTextureFormat swapchain_format = formats[0];
|
|
SDL_Log("Using swapchain format: %d (of %d available)", swapchain_format, num_formats);
|
|
|
|
/* Log all available formats for debugging */
|
|
for (int f = 0; f < num_formats && formats[f] != SDL_GPU_TEXTUREFORMAT_INVALID; f++) {
|
|
SDL_Log(" Available format [%d]: %d", f, formats[f]);
|
|
}
|
|
SDL_free(formats);
|
|
|
|
for (Uint32 i = 0; i < view_count; i++) {
|
|
xr_views[i].type = XR_TYPE_VIEW;
|
|
xr_views[i].pose.orientation.w = 1.0f;
|
|
|
|
SDL_Log("Eye %" SDL_PRIu32 ": recommended %ux%u", i,
|
|
(unsigned int)view_configs[i].recommendedImageRectWidth,
|
|
(unsigned int)view_configs[i].recommendedImageRectHeight);
|
|
|
|
/* Create swapchain using OpenXR's XrSwapchainCreateInfo */
|
|
XrSwapchainCreateInfo swapchain_info = { XR_TYPE_SWAPCHAIN_CREATE_INFO };
|
|
swapchain_info.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT;
|
|
swapchain_info.format = 0; /* Ignored - SDL uses the format parameter */
|
|
swapchain_info.sampleCount = 1;
|
|
swapchain_info.width = view_configs[i].recommendedImageRectWidth;
|
|
swapchain_info.height = view_configs[i].recommendedImageRectHeight;
|
|
swapchain_info.faceCount = 1;
|
|
swapchain_info.arraySize = 1;
|
|
swapchain_info.mipCount = 1;
|
|
|
|
result = SDL_CreateGPUXRSwapchain(
|
|
gpu_device,
|
|
xr_session,
|
|
&swapchain_info,
|
|
swapchain_format,
|
|
&vr_swapchains[i].swapchain,
|
|
&vr_swapchains[i].images);
|
|
|
|
vr_swapchains[i].format = swapchain_format;
|
|
|
|
if (XR_FAILED(result)) {
|
|
SDL_Log("Failed to create swapchain %" SDL_PRIu32, i);
|
|
SDL_free(view_configs);
|
|
return false;
|
|
}
|
|
|
|
/* Get image count by enumerating swapchain images */
|
|
result = pfn_xrEnumerateSwapchainImages(vr_swapchains[i].swapchain, 0, &vr_swapchains[i].image_count, NULL);
|
|
if (XR_FAILED(result)) {
|
|
vr_swapchains[i].image_count = 3; /* Assume 3 if we can't query */
|
|
}
|
|
|
|
vr_swapchains[i].size.width = (int32_t)swapchain_info.width;
|
|
vr_swapchains[i].size.height = (int32_t)swapchain_info.height;
|
|
|
|
/* Create local depth texture for this eye
|
|
* Per PR #14837: Depth buffers are "really recommended" for XR apps.
|
|
* Using a local depth texture (not XR-managed) is the simplest approach
|
|
* for proper z-ordering without requiring XR_KHR_composition_layer_depth. */
|
|
SDL_GPUTextureCreateInfo depth_info = {
|
|
.type = SDL_GPU_TEXTURETYPE_2D,
|
|
.format = DEPTH_FORMAT,
|
|
.width = swapchain_info.width,
|
|
.height = swapchain_info.height,
|
|
.layer_count_or_depth = 1,
|
|
.num_levels = 1,
|
|
.sample_count = SDL_GPU_SAMPLECOUNT_1,
|
|
.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
|
.props = 0
|
|
};
|
|
vr_swapchains[i].depth_texture = SDL_CreateGPUTexture(gpu_device, &depth_info);
|
|
if (!vr_swapchains[i].depth_texture) {
|
|
SDL_Log("Failed to create depth texture for eye %" SDL_PRIu32 ": %s", i, SDL_GetError());
|
|
SDL_free(view_configs);
|
|
return false;
|
|
}
|
|
|
|
SDL_Log("Created swapchain %" SDL_PRIu32 ": %" SDL_PRIs32 "x%" SDL_PRIs32 ", %" SDL_PRIu32 " images, with depth buffer",
|
|
i, vr_swapchains[i].size.width, vr_swapchains[i].size.height,
|
|
vr_swapchains[i].image_count);
|
|
}
|
|
|
|
SDL_free(view_configs);
|
|
|
|
/* Create the pipeline using the swapchain format */
|
|
if (view_count > 0 && pipeline == NULL) {
|
|
if (!create_pipeline(vr_swapchains[0].format)) {
|
|
return false;
|
|
}
|
|
if (!create_cube_buffers()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* XR Event Handling
|
|
* ======================================================================== */
|
|
|
|
static void handle_xr_events(void)
|
|
{
|
|
XrEventDataBuffer event_buffer = { XR_TYPE_EVENT_DATA_BUFFER };
|
|
|
|
while (pfn_xrPollEvent(xr_instance, &event_buffer) == XR_SUCCESS) {
|
|
switch (event_buffer.type) {
|
|
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
|
|
XrEventDataSessionStateChanged *state_event =
|
|
(XrEventDataSessionStateChanged*)&event_buffer;
|
|
|
|
SDL_Log("Session state changed: %d", state_event->state);
|
|
|
|
switch (state_event->state) {
|
|
case XR_SESSION_STATE_READY: {
|
|
XrSessionBeginInfo begin_info = { XR_TYPE_SESSION_BEGIN_INFO };
|
|
begin_info.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
|
|
|
XrResult result = pfn_xrBeginSession(xr_session, &begin_info);
|
|
if (XR_SUCCEEDED(result)) {
|
|
SDL_Log("XR Session begun!");
|
|
xr_session_running = true;
|
|
|
|
/* Create swapchains now that session is ready */
|
|
if (!create_swapchains()) {
|
|
SDL_Log("Failed to create swapchains");
|
|
xr_should_quit = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case XR_SESSION_STATE_STOPPING:
|
|
pfn_xrEndSession(xr_session);
|
|
xr_session_running = false;
|
|
break;
|
|
case XR_SESSION_STATE_EXITING:
|
|
case XR_SESSION_STATE_LOSS_PENDING:
|
|
xr_should_quit = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
|
|
xr_should_quit = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
event_buffer.type = XR_TYPE_EVENT_DATA_BUFFER;
|
|
}
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Rendering
|
|
* ======================================================================== */
|
|
|
|
static void render_frame(void)
|
|
{
|
|
if (!xr_session_running) return;
|
|
|
|
XrFrameState frame_state = { XR_TYPE_FRAME_STATE };
|
|
XrFrameWaitInfo wait_info = { XR_TYPE_FRAME_WAIT_INFO };
|
|
|
|
XrResult result = pfn_xrWaitFrame(xr_session, &wait_info, &frame_state);
|
|
if (XR_FAILED(result)) return;
|
|
|
|
XrFrameBeginInfo begin_info = { XR_TYPE_FRAME_BEGIN_INFO };
|
|
result = pfn_xrBeginFrame(xr_session, &begin_info);
|
|
if (XR_FAILED(result)) return;
|
|
|
|
XrCompositionLayerProjectionView *proj_views = NULL;
|
|
XrCompositionLayerProjection layer = { XR_TYPE_COMPOSITION_LAYER_PROJECTION };
|
|
Uint32 layer_count = 0;
|
|
const XrCompositionLayerBaseHeader *layers[1] = {0};
|
|
|
|
if (frame_state.shouldRender && view_count > 0 && vr_swapchains != NULL) {
|
|
/* Update animation time */
|
|
Uint64 now = SDL_GetTicks();
|
|
if (last_ticks == 0) last_ticks = now;
|
|
float delta = (float)(now - last_ticks) / 1000.0f;
|
|
last_ticks = now;
|
|
anim_time += delta;
|
|
|
|
/* Locate views */
|
|
XrViewState view_state = { XR_TYPE_VIEW_STATE };
|
|
XrViewLocateInfo locate_info = { XR_TYPE_VIEW_LOCATE_INFO };
|
|
locate_info.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
|
locate_info.displayTime = frame_state.predictedDisplayTime;
|
|
locate_info.space = xr_local_space;
|
|
|
|
Uint32 view_count_output;
|
|
result = pfn_xrLocateViews(xr_session, &locate_info, &view_state, view_count, &view_count_output, xr_views);
|
|
if (XR_FAILED(result)) {
|
|
SDL_Log("xrLocateViews failed");
|
|
goto endFrame;
|
|
}
|
|
|
|
proj_views = SDL_calloc(view_count, sizeof(XrCompositionLayerProjectionView));
|
|
|
|
SDL_GPUCommandBuffer *cmd_buf = SDL_AcquireGPUCommandBuffer(gpu_device);
|
|
|
|
/* Multi-pass stereo: render each eye separately */
|
|
for (Uint32 i = 0; i < view_count; i++) {
|
|
VRSwapchain *swapchain = &vr_swapchains[i];
|
|
|
|
/* Acquire swapchain image */
|
|
Uint32 image_index;
|
|
XrSwapchainImageAcquireInfo acquire_info = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
|
result = pfn_xrAcquireSwapchainImage(swapchain->swapchain, &acquire_info, &image_index);
|
|
if (XR_FAILED(result)) continue;
|
|
|
|
XrSwapchainImageWaitInfo wait_image_info = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
|
|
wait_image_info.timeout = XR_INFINITE_DURATION;
|
|
result = pfn_xrWaitSwapchainImage(swapchain->swapchain, &wait_image_info);
|
|
if (XR_FAILED(result)) {
|
|
XrSwapchainImageReleaseInfo release_info = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
|
pfn_xrReleaseSwapchainImage(swapchain->swapchain, &release_info);
|
|
continue;
|
|
}
|
|
|
|
/* Render the scene to this eye */
|
|
SDL_GPUTexture *target_texture = swapchain->images[image_index];
|
|
|
|
/* Build view and projection matrices from XR pose/fov */
|
|
Mat4 view_matrix = Mat4_FromXrPose(xr_views[i].pose);
|
|
Mat4 proj_matrix = Mat4_Projection(xr_views[i].fov, 0.05f, 100.0f);
|
|
|
|
SDL_GPUColorTargetInfo color_target = {0};
|
|
color_target.texture = target_texture;
|
|
color_target.load_op = SDL_GPU_LOADOP_CLEAR;
|
|
color_target.store_op = SDL_GPU_STOREOP_STORE;
|
|
/* Dark blue background */
|
|
color_target.clear_color.r = 0.05f;
|
|
color_target.clear_color.g = 0.05f;
|
|
color_target.clear_color.b = 0.15f;
|
|
color_target.clear_color.a = 1.0f;
|
|
|
|
/* Set up depth target for proper z-ordering */
|
|
SDL_GPUDepthStencilTargetInfo depth_target = {0};
|
|
depth_target.texture = swapchain->depth_texture;
|
|
depth_target.clear_depth = 1.0f; /* Far plane */
|
|
depth_target.load_op = SDL_GPU_LOADOP_CLEAR;
|
|
depth_target.store_op = SDL_GPU_STOREOP_DONT_CARE; /* We don't need to preserve depth */
|
|
depth_target.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE;
|
|
depth_target.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE;
|
|
depth_target.cycle = true; /* Allow GPU to cycle the texture for efficiency */
|
|
|
|
SDL_GPURenderPass *render_pass = SDL_BeginGPURenderPass(cmd_buf, &color_target, 1, &depth_target);
|
|
|
|
if (pipeline && vertex_buffer && index_buffer) {
|
|
SDL_BindGPUGraphicsPipeline(render_pass, pipeline);
|
|
|
|
SDL_GPUViewport viewport = {0, 0, (float)swapchain->size.width, (float)swapchain->size.height, 0, 1};
|
|
SDL_SetGPUViewport(render_pass, &viewport);
|
|
|
|
SDL_Rect scissor = {0, 0, swapchain->size.width, swapchain->size.height};
|
|
SDL_SetGPUScissor(render_pass, &scissor);
|
|
|
|
SDL_GPUBufferBinding vertex_binding = {vertex_buffer, 0};
|
|
SDL_BindGPUVertexBuffers(render_pass, 0, &vertex_binding, 1);
|
|
|
|
SDL_GPUBufferBinding index_binding = {index_buffer, 0};
|
|
SDL_BindGPUIndexBuffer(render_pass, &index_binding, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
|
|
|
/* Draw each cube */
|
|
for (int cube_idx = 0; cube_idx < NUM_CUBES; cube_idx++) {
|
|
float rot = anim_time * cube_speeds[cube_idx];
|
|
Vec3 pos = cube_positions[cube_idx];
|
|
|
|
/* Build model matrix: scale -> rotateY -> rotateX -> translate */
|
|
Mat4 scale = Mat4_Scale(cube_scales[cube_idx]);
|
|
Mat4 rotY = Mat4_RotationY(rot);
|
|
Mat4 rotX = Mat4_RotationX(rot * 0.7f);
|
|
Mat4 trans = Mat4_Translation(pos.x, pos.y, pos.z);
|
|
|
|
Mat4 model = Mat4_Multiply(Mat4_Multiply(Mat4_Multiply(scale, rotY), rotX), trans);
|
|
Mat4 mv = Mat4_Multiply(model, view_matrix);
|
|
Mat4 mvp = Mat4_Multiply(mv, proj_matrix);
|
|
|
|
SDL_PushGPUVertexUniformData(cmd_buf, 0, &mvp, sizeof(mvp));
|
|
SDL_DrawGPUIndexedPrimitives(render_pass, 36, 1, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
SDL_EndGPURenderPass(render_pass);
|
|
|
|
/* Release swapchain image */
|
|
XrSwapchainImageReleaseInfo release_info = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
|
pfn_xrReleaseSwapchainImage(swapchain->swapchain, &release_info);
|
|
|
|
/* Set up projection view */
|
|
proj_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
|
|
proj_views[i].pose = xr_views[i].pose;
|
|
proj_views[i].fov = xr_views[i].fov;
|
|
proj_views[i].subImage.swapchain = swapchain->swapchain;
|
|
proj_views[i].subImage.imageRect.offset.x = 0;
|
|
proj_views[i].subImage.imageRect.offset.y = 0;
|
|
proj_views[i].subImage.imageRect.extent = swapchain->size;
|
|
proj_views[i].subImage.imageArrayIndex = 0;
|
|
}
|
|
|
|
SDL_SubmitGPUCommandBuffer(cmd_buf);
|
|
|
|
layer.space = xr_local_space;
|
|
layer.viewCount = view_count;
|
|
layer.views = proj_views;
|
|
layers[0] = (XrCompositionLayerBaseHeader*)&layer;
|
|
layer_count = 1;
|
|
}
|
|
|
|
endFrame:;
|
|
XrFrameEndInfo end_info = { XR_TYPE_FRAME_END_INFO };
|
|
end_info.displayTime = frame_state.predictedDisplayTime;
|
|
end_info.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
|
end_info.layerCount = layer_count;
|
|
end_info.layers = layers;
|
|
|
|
pfn_xrEndFrame(xr_session, &end_info);
|
|
|
|
if (proj_views) SDL_free(proj_views);
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Main
|
|
* ======================================================================== */
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
(void)argc;
|
|
(void)argv;
|
|
|
|
SDL_Log("SDL GPU OpenXR Spinning Cubes Test starting...");
|
|
SDL_Log("Stereo rendering mode: Multi-pass (one render pass per eye)");
|
|
|
|
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
|
|
SDL_Log("SDL_Init failed: %s", SDL_GetError());
|
|
return 1;
|
|
}
|
|
|
|
SDL_Log("SDL initialized");
|
|
|
|
/* Create GPU device with OpenXR enabled */
|
|
SDL_Log("Creating GPU device with OpenXR enabled...");
|
|
|
|
SDL_PropertiesID props = SDL_CreateProperties();
|
|
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, true);
|
|
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, true);
|
|
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, true);
|
|
/* Enable XR - SDL will create the OpenXR instance for us */
|
|
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, true);
|
|
SDL_SetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_INSTANCE_POINTER, &xr_instance);
|
|
SDL_SetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_SYSTEM_ID_POINTER, &xr_system_id);
|
|
SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_APPLICATION_NAME_STRING, "SDL XR Spinning Cubes Test");
|
|
SDL_SetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_APPLICATION_VERSION_NUMBER, 1);
|
|
|
|
gpu_device = SDL_CreateGPUDeviceWithProperties(props);
|
|
SDL_DestroyProperties(props);
|
|
|
|
if (!gpu_device) {
|
|
SDL_Log("Failed to create GPU device: %s", SDL_GetError());
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
/* Load OpenXR function pointers */
|
|
if (!load_xr_functions()) {
|
|
SDL_Log("Failed to load XR functions");
|
|
quit(1);
|
|
}
|
|
|
|
/* Initialize XR session */
|
|
if (!init_xr_session()) {
|
|
SDL_Log("Failed to init XR session");
|
|
quit(1);
|
|
}
|
|
|
|
SDL_Log("Entering main loop... Put on your VR headset!");
|
|
|
|
/* Main loop */
|
|
while (!xr_should_quit) {
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
if (event.type == SDL_EVENT_QUIT) {
|
|
xr_should_quit = true;
|
|
}
|
|
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE) {
|
|
xr_should_quit = true;
|
|
}
|
|
}
|
|
|
|
handle_xr_events();
|
|
render_frame();
|
|
}
|
|
|
|
quit(0);
|
|
return 0;
|
|
}
|