| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983 |
- /*
- Copyright (C) 1997-2026 Sam Lantinga <[email protected]>
- 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;
- }
|