/* Dependencies: libsdl2-dev libvulkan-dev libgl1-mesa-dev 1. Get the math_3d header: wget https://raw.githubusercontent.com/arkanis/single-header-file-c-libs/master/math_3d.h 2. Compile: gcc -O3 -ggdb color_attachment.c -lm -lGL -lSDL2 -lvulkan 3. run VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ./a.out */ #define GL_GLEXT_PROTOTYPES #define GL3_PROTOTYPES #include #include #include #include #include #include #include #include #include #include "math_3d.h" #include struct VK { VkInstance instance; VkPhysicalDevice physical_device; VkDevice device; PFN_vkGetMemoryFdKHR extVkGetMemoryFdKHR; VkPhysicalDeviceMemoryProperties memory_properties; VkPhysicalDeviceProperties physical_props; uint32_t transfer_queue_family_index; VkQueue transfer_queue; VkFence transfer_queue_fence; VkCommandPool pool; VkFence pool_fence; }; #define CHECK_VK_RESULT(result, msg, ret) \ if (result != VK_SUCCESS) { \ printf("Failed: %s\n", msg); \ return ret; \ } #define ENUM_TO_STR(r) case r: return #r static const char* gl_error_string (GLenum code) { switch (code) { ENUM_TO_STR(GL_NO_ERROR); ENUM_TO_STR(GL_INVALID_ENUM); ENUM_TO_STR(GL_INVALID_VALUE); ENUM_TO_STR(GL_INVALID_OPERATION); ENUM_TO_STR(GL_INVALID_FRAMEBUFFER_OPERATION); ENUM_TO_STR(GL_OUT_OF_MEMORY); ENUM_TO_STR(GL_STACK_UNDERFLOW); ENUM_TO_STR(GL_STACK_OVERFLOW); default: return "UNKNOWN GL Error"; } } static void gl_check_error (char* prefix) { GLenum err = 0 ; while((err = glGetError ()) != GL_NO_ERROR) printf ("GL ERROR: %s - %s\n", prefix, gl_error_string (err)); } static bool find_queue_index_for_flags (VkQueueFlagBits flags, VkQueueFlagBits exclude_flags, uint32_t num_queues, VkQueueFamilyProperties *queue_family_props, uint32_t *out_index) { uint32_t i = 0; for (i = 0; i < num_queues; i++) if (queue_family_props[i].queueFlags & flags && !(queue_family_props[i].queueFlags & exclude_flags)) break; if (i >= num_queues) return false; *out_index = i; return true; } static bool init_vk(struct VK *vk) { const char *required_vk_instance_extensions[] = { VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, }; const char *required_vk_device_extensions[] = { VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME, }; VkResult res; VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "opengl depth texture example", .applicationVersion = 1, .pEngineName = NULL, .engineVersion = 1, .apiVersion = VK_MAKE_VERSION(1, 1, 0), }; VkInstanceCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .enabledExtensionCount = sizeof(required_vk_instance_extensions) / sizeof(required_vk_instance_extensions[0]), .ppEnabledExtensionNames = required_vk_instance_extensions, .flags = 0, .pApplicationInfo = &app_info, }; res = vkCreateInstance(&create_info, NULL, &vk->instance); CHECK_VK_RESULT(res, "vkCreateInstance", false); uint32_t num_physical_devices = 0; res = vkEnumeratePhysicalDevices(vk->instance, &num_physical_devices, NULL); CHECK_VK_RESULT(res, "vkEnumeratePhysicalDevices 1", false); VkPhysicalDevice physical_devices[num_physical_devices]; res = vkEnumeratePhysicalDevices(vk->instance, &num_physical_devices, physical_devices); CHECK_VK_RESULT(res, "vkEnumeratePhysicalDevices 2", false); vk->physical_device = physical_devices[0]; uint32_t num_queues = 0; vkGetPhysicalDeviceQueueFamilyProperties (vk->physical_device, &num_queues, 0); VkQueueFamilyProperties queue_family_props[num_queues]; vkGetPhysicalDeviceQueueFamilyProperties (vk->physical_device, &num_queues, queue_family_props); if (!find_queue_index_for_flags (VK_QUEUE_TRANSFER_BIT, (VkQueueFlagBits)0,num_queues, queue_family_props, &vk->transfer_queue_family_index)) { printf("no transfer qeue found\n"); return false; } printf("transfer queue index %d / %d\n", vk->transfer_queue_family_index, num_queues); VkDeviceQueueCreateInfo queue_create_infos[1] = {{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = vk->transfer_queue_family_index, .queueCount = 1, .pQueuePriorities = (const float[]) { 1.0f } }}; VkDeviceCreateInfo device_create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .enabledExtensionCount = sizeof(required_vk_device_extensions) / sizeof(required_vk_device_extensions[0]), .ppEnabledExtensionNames = required_vk_device_extensions, .queueCreateInfoCount = 1, .pQueueCreateInfos = queue_create_infos, }; res = vkCreateDevice(vk->physical_device, &device_create_info, NULL, &vk->device); CHECK_VK_RESULT(res, "vkCreateDevice", false); vkGetPhysicalDeviceMemoryProperties (vk->physical_device, &vk->memory_properties); vkGetPhysicalDeviceProperties (vk->physical_device, &vk->physical_props); vkGetDeviceQueue (vk->device, vk->transfer_queue_family_index, 0, &vk->transfer_queue); VkCommandPoolCreateInfo command_pool_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = vk->transfer_queue_family_index, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT }; res = vkCreateCommandPool (vk->device, &command_pool_info, NULL, &vk->pool); CHECK_VK_RESULT(res, "vkCreateCommandPool", false); VkFenceCreateInfo fence_info = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; res = vkCreateFence (vk->device, &fence_info, NULL, &vk->pool_fence); CHECK_VK_RESULT(res, "vkCreateFence", false); return true; } static bool gulkan_device_memory_type_from_properties ( struct VK *vk, uint32_t memory_type_bits, VkMemoryPropertyFlags memory_property_flags, uint32_t *type_index_out) { for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) { if ((memory_type_bits & 1) == 1) { if ((vk->memory_properties.memoryTypes[i].propertyFlags & memory_property_flags) == memory_property_flags) { *type_index_out = i; return true; } } memory_type_bits >>= 1; } /* Could not find matching memory type.*/ return false; } bool gulkan_device_get_memory_fd (struct VK *vk, VkDeviceMemory image_memory, int *fd) { if (!vk->extVkGetMemoryFdKHR) vk->extVkGetMemoryFdKHR = (PFN_vkGetMemoryFdKHR) vkGetDeviceProcAddr (vk->device, "vkGetMemoryFdKHR"); if (!vk->extVkGetMemoryFdKHR) { printf ("Gulkan Device: Could not load vkGetMemoryFdKHR\n"); return false; } VkMemoryGetFdInfoKHR vkFDInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, .memory = image_memory, .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR }; if (vk->extVkGetMemoryFdKHR (vk->device, &vkFDInfo, fd) != VK_SUCCESS) { printf ("Gulkan Device: Could not get file descriptor for memory!\n"); return false; } return true; } static VkAccessFlags _get_access_flags (VkImageLayout layout) { switch (layout) { case VK_IMAGE_LAYOUT_UNDEFINED: return 0; case VK_IMAGE_LAYOUT_PREINITIALIZED: return VK_ACCESS_HOST_WRITE_BIT; case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: return VK_ACCESS_TRANSFER_READ_BIT; case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: return VK_ACCESS_TRANSFER_WRITE_BIT; case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: return VK_ACCESS_SHADER_READ_BIT; default: printf ("Unhandled access mask case for layout %d.\n", layout); } return 0; } bool comp_is_format_supported(struct VK *vk, VkFormat format) { VkFormatProperties prop; vkGetPhysicalDeviceFormatProperties(vk->physical_device, format, &prop); // This is a fairly crude way of checking support, // but works well enough. return prop.optimalTilingFeatures != 0; } enum ImageUsage { IMAGE_USAGE_COLOR, IMAGE_USAGE_DEPTH, } ; static bool make_exported_texture(struct VK *vk, VkFormat format, GLuint gl_internal_format, uint32_t w, uint32_t h, GLuint *gl_tex, enum ImageUsage image_usage) { VkResult res; VkImageUsageFlags usage = 0; switch (image_usage) { case IMAGE_USAGE_COLOR: usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT #if 1 | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT #endif ; break; case IMAGE_USAGE_DEPTH: usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; break; } VkImageFormatProperties format_props; res = vkGetPhysicalDeviceImageFormatProperties(vk->physical_device, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, image_usage, 0, &format_props); CHECK_VK_RESULT(res, "vkGetPhysicalDeviceImageFormatProperties", false); printf("format_props: max arr layers %d, max ext %dx%d, max mips %d, res size %luu, sample cound %d\n", format_props.maxArrayLayers, format_props.maxExtent.width, format_props.maxExtent.height, format_props.maxMipLevels, format_props.maxResourceSize, format_props.sampleCounts); printf("format is supported: %d\n", comp_is_format_supported(vk, format)); /* we can also export the memory of the image without using this struct but * the spec makes it sound like we should use it */ VkExternalMemoryImageCreateInfo external_memory_image_create_info = { .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT }; VkImageCreateInfo image_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = &external_memory_image_create_info, .imageType = VK_IMAGE_TYPE_2D, .extent = { .width = w, .height = h, .depth = 1, }, .mipLevels = 1, .arrayLayers = 1, .format = format, .tiling = VK_IMAGE_TILING_OPTIMAL, .samples = VK_SAMPLE_COUNT_1_BIT, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED }; VkImage vk_image; res = vkCreateImage (vk->device, &image_info, NULL, &vk_image); CHECK_VK_RESULT(res, "vkCreateImage", false); VkImageMemoryRequirementsInfo2 imageMemReqInfo2 = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2 }; imageMemReqInfo2.image = vk_image; VkMemoryRequirements2 memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2 }; VkMemoryDedicatedRequirements memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS }; memReq2.pNext = &memDedicatedReq; vkGetImageMemoryRequirements2(vk->device, &imageMemReqInfo2, &memReq2); VkBool32 dedicatedAllocation = (memDedicatedReq.requiresDedicatedAllocation != VK_FALSE) || (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); VkMemoryDedicatedAllocateInfoKHR dedicated_memory_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR, .pNext = NULL, .image = vk_image, .buffer = VK_NULL_HANDLE }; VkMemoryAllocateInfo memory_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = dedicatedAllocation ? &dedicated_memory_info : NULL, .allocationSize = memReq2.memoryRequirements.size, /* filled in later */ .memoryTypeIndex = 0 }; VkMemoryPropertyFlags full_memory_property_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; bool full_flags_available = gulkan_device_memory_type_from_properties (vk, memReq2.memoryRequirements.memoryTypeBits, full_memory_property_flags, &memory_info.memoryTypeIndex); if (!full_flags_available) { VkMemoryPropertyFlags fallback_memory_property_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; if (!gulkan_device_memory_type_from_properties (vk, memReq2.memoryRequirements.memoryTypeBits, fallback_memory_property_flags, &memory_info.memoryTypeIndex)) { printf("VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT" " memory flags not available.\n"); return NULL; } } printf("exported vk image size is %ld, type %d\n", memory_info.allocationSize, memory_info.memoryTypeIndex ); VkDeviceMemory vk_image_memory; res = vkAllocateMemory (vk->device, &memory_info, NULL, &vk_image_memory); CHECK_VK_RESULT(res, "vkAllocateMemory", false); res = vkBindImageMemory (vk->device, vk_image, vk_image_memory, 0); CHECK_VK_RESULT(res, "vkBindImageMemory", false); int fd; if (!gulkan_device_get_memory_fd (vk, vk_image_memory, &fd)) { printf ("Could not get file descriptor for memory!\n"); return false; } printf("exported vk image to fd %d\n", fd); static PFNGLCREATEMEMORYOBJECTSEXTPROC glCreateMemoryObjectsEXT = NULL; if (glCreateMemoryObjectsEXT== NULL) { glCreateMemoryObjectsEXT = (PFNGLCREATEMEMORYOBJECTSEXTPROC) SDL_GL_GetProcAddress("glCreateMemoryObjectsEXT"); } static PFNGLMEMORYOBJECTPARAMETERIVEXTPROC glMemoryObjectParameterivEXT = NULL; if (glMemoryObjectParameterivEXT== NULL) { glMemoryObjectParameterivEXT = (PFNGLMEMORYOBJECTPARAMETERIVEXTPROC) SDL_GL_GetProcAddress("glMemoryObjectParameterivEXT"); } static PFNGLIMPORTMEMORYFDEXTPROC glImportMemoryFdEXT = NULL; if (glImportMemoryFdEXT == NULL) { glImportMemoryFdEXT = (PFNGLIMPORTMEMORYFDEXTPROC) SDL_GL_GetProcAddress("glImportMemoryFdEXT"); } static PFNGLTEXSTORAGEMEM2DEXTPROC glTexStorageMem2DEXT = NULL; if (glTexStorageMem2DEXT == NULL) { glTexStorageMem2DEXT = (PFNGLTEXSTORAGEMEM2DEXTPROC) SDL_GL_GetProcAddress("glTexStorageMem2DEXT"); } GLint gl_dedicated_mem = dedicatedAllocation ? GL_TRUE : GL_FALSE; printf("gl dedicated: %d | req %d | pref %d\n", dedicatedAllocation, memDedicatedReq.requiresDedicatedAllocation, memDedicatedReq.prefersDedicatedAllocation); GLuint gl_mem_object; glCreateMemoryObjectsEXT (1, &gl_mem_object); gl_check_error ("glCreateMemoryObjectsEXT"); printf ("created memory object id: %d\n", gl_mem_object); glMemoryObjectParameterivEXT (gl_mem_object, GL_DEDICATED_MEMORY_OBJECT_EXT, &gl_dedicated_mem); gl_check_error ("glCreateMemoryObjectsEXT"); /* Note: ImportMemoryFd takes ownership of the fd from the Vulkan driver. * The Vulkan driver is now free to reuse the fd number */ glImportMemoryFdEXT (gl_mem_object, memReq2.memoryRequirements.size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd); gl_check_error ("glImportMemoryFdEXT"); printf ("Imported texture from fd %d\n", fd); GLuint gl_texture; glGenTextures (1, &gl_texture); glBindTexture (GL_TEXTURE_2D, gl_texture); gl_check_error ("glBindTexture"); glTexParameteri (GL_TEXTURE_2D,GL_TEXTURE_TILING_EXT, GL_OPTIMAL_TILING_EXT); gl_check_error ("glTexParameteri GL_OPTIMAL_TILING_EXT"); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl_check_error ("glTexParameteri GL_TEXTURE_MIN_FILTER"); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl_check_error ("glTexParameteri GL_TEXTURE_MAG_FILTER"); glTexStorageMem2DEXT (GL_TEXTURE_2D, 1, gl_internal_format, w, h, gl_mem_object, 0); gl_check_error ("glTexStorageMem2DEXT"); glBindTexture (GL_TEXTURE_2D, 0); *gl_tex = gl_texture; return true; } static const char* vertexshader = "#version 330 core\n" "#extension GL_ARB_explicit_uniform_location : require\n" "layout(location = 0) in vec3 aPos;\n" "layout(location = 2) uniform mat4 model;\n" "layout(location = 3) uniform mat4 view;\n" "layout(location = 4) uniform mat4 proj;\n" "layout(location = 5) in vec2 aColor;\n" "out vec2 vertexColor;\n" "void main() {\n" " gl_Position = proj * view * model * vec4(aPos.x, aPos.y, aPos.z, " "1.0);\n" " vertexColor = aColor;\n" "}\n"; static const char* fragmentshader = "#version 330 core\n" "#extension GL_ARB_explicit_uniform_location : require\n" "layout(location = 0) out vec4 FragColor;\n" "layout(location = 1) uniform vec3 uniformColor;\n" "in vec2 vertexColor;\n" "void main() {\n" " FragColor = (uniformColor.x < 0.01 && uniformColor.y < 0.01 && " "uniformColor.z < 0.01) ? vec4(vertexColor, 1.0, 1.0) : vec4(uniformColor, " "1.0);\n" "}\n"; int init_gl(uint32_t view_count, uint32_t* swapchain_lengths, GLuint*** framebuffers, GLuint* shader_program_id, GLuint* VAO) { /* Allocate resources that we use for our own rendering. * We will bind framebuffers to the runtime provided textures for rendering. * For this, we create one framebuffer per OpenGL texture. * This is not mandated by OpenXR, other ways to render to textures will work too. */ *framebuffers = malloc(sizeof(GLuint*) * view_count); for (uint32_t i = 0; i < view_count; i++) { (*framebuffers)[i] = malloc(sizeof(GLuint) * swapchain_lengths[i]); glGenFramebuffers(swapchain_lengths[i], (*framebuffers)[i]); } GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER); const GLchar* vertex_shader_source[1]; vertex_shader_source[0] = vertexshader; // printf("Vertex Shader:\n%s\n", vertexShaderSource); glShaderSource(vertex_shader_id, 1, vertex_shader_source, NULL); glCompileShader(vertex_shader_id); int vertex_compile_res; glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &vertex_compile_res); if (!vertex_compile_res) { char info_log[512]; glGetShaderInfoLog(vertex_shader_id, 512, NULL, info_log); printf("Vertex Shader failed to compile: %s\n", info_log); return 1; } else { printf("Successfully compiled vertex shader!\n"); } GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER); const GLchar* fragment_shader_source[1]; fragment_shader_source[0] = fragmentshader; glShaderSource(fragment_shader_id, 1, fragment_shader_source, NULL); glCompileShader(fragment_shader_id); int fragment_compile_res; glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &fragment_compile_res); if (!fragment_compile_res) { char info_log[512]; glGetShaderInfoLog(fragment_shader_id, 512, NULL, info_log); printf("Fragment Shader failed to compile: %s\n", info_log); return 1; } else { printf("Successfully compiled fragment shader!\n"); } *shader_program_id = glCreateProgram(); glAttachShader(*shader_program_id, vertex_shader_id); glAttachShader(*shader_program_id, fragment_shader_id); glLinkProgram(*shader_program_id); GLint shader_program_res; glGetProgramiv(*shader_program_id, GL_LINK_STATUS, &shader_program_res); if (!shader_program_res) { char info_log[512]; glGetProgramInfoLog(*shader_program_id, 512, NULL, info_log); printf("Shader Program failed to link: %s\n", info_log); return 1; } else { printf("Successfully linked shader program!\n"); } glDeleteShader(vertex_shader_id); glDeleteShader(fragment_shader_id); float vertices[] = {-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f}; GLuint VBOs[1]; glGenBuffers(1, VBOs); glGenVertexArrays(1, VAO); glBindVertexArray(*VAO); glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); glVertexAttribPointer(5, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(5); glEnable(GL_DEPTH_TEST); return 0; } static SDL_Window* desktop_window; static SDL_GLContext gl_context; static PFNGLBLITNAMEDFRAMEBUFFERPROC _glBlitNamedFramebuffer; void GLAPIENTRY MessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), type, severity, message); } bool init_sdl_window(int w, int h) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("Unable to initialize SDL"); return false; } SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0); /* Create our window centered at half the VR resolution */ desktop_window = SDL_CreateWindow("OpenXR Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w / 2, h / 2, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); if (!desktop_window) { printf("Unable to create window"); return false; } gl_context = SDL_GL_CreateContext(desktop_window); glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(MessageCallback, 0); SDL_GL_SetSwapInterval(0); _glBlitNamedFramebuffer = (PFNGLBLITNAMEDFRAMEBUFFERPROC)glXGetProcAddressARB((GLubyte*)"glBlitNamedFramebuffer"); return true; } typedef struct XrMatrix4x4f { float m[16]; } XrMatrix4x4f; typedef struct XrFovf { float angleLeft; float angleRight; float angleUp; float angleDown; } XrFovf; typedef struct XrQuaternionf { float x; float y; float z; float w; } XrQuaternionf; typedef struct XrVector3f { float x; float y; float z; } XrVector3f; inline static void XrMatrix4x4f_CreateProjectionFov(XrMatrix4x4f* result, const XrFovf fov, const float nearZ, const float farZ) { const float tanAngleLeft = tanf(fov.angleLeft); const float tanAngleRight = tanf(fov.angleRight); const float tanAngleDown = tanf(fov.angleDown); const float tanAngleUp = tanf(fov.angleUp); const float tanAngleWidth = tanAngleRight - tanAngleLeft; // Set to tanAngleDown - tanAngleUp for a clip space with positive Y // down (Vulkan). Set to tanAngleUp - tanAngleDown for a clip space with // positive Y up (OpenGL / D3D / Metal). const float tanAngleHeight = (tanAngleUp - tanAngleDown); // Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES). // Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal). const float offsetZ = nearZ; if (farZ <= nearZ) { // place the far plane at infinity result->m[0] = 2 / tanAngleWidth; result->m[4] = 0; result->m[8] = (tanAngleRight + tanAngleLeft) / tanAngleWidth; result->m[12] = 0; result->m[1] = 0; result->m[5] = 2 / tanAngleHeight; result->m[9] = (tanAngleUp + tanAngleDown) / tanAngleHeight; result->m[13] = 0; result->m[2] = 0; result->m[6] = 0; result->m[10] = -1; result->m[14] = -(nearZ + offsetZ); result->m[3] = 0; result->m[7] = 0; result->m[11] = -1; result->m[15] = 0; } else { // normal projection result->m[0] = 2 / tanAngleWidth; result->m[4] = 0; result->m[8] = (tanAngleRight + tanAngleLeft) / tanAngleWidth; result->m[12] = 0; result->m[1] = 0; result->m[5] = 2 / tanAngleHeight; result->m[9] = (tanAngleUp + tanAngleDown) / tanAngleHeight; result->m[13] = 0; result->m[2] = 0; result->m[6] = 0; result->m[10] = -(farZ + offsetZ) / (farZ - nearZ); result->m[14] = -(farZ * (nearZ + offsetZ)) / (farZ - nearZ); result->m[3] = 0; result->m[7] = 0; result->m[11] = -1; result->m[15] = 0; } } inline static void XrMatrix4x4f_CreateFromQuaternion(XrMatrix4x4f* result, const XrQuaternionf* quat) { const float x2 = quat->x + quat->x; const float y2 = quat->y + quat->y; const float z2 = quat->z + quat->z; const float xx2 = quat->x * x2; const float yy2 = quat->y * y2; const float zz2 = quat->z * z2; const float yz2 = quat->y * z2; const float wx2 = quat->w * x2; const float xy2 = quat->x * y2; const float wz2 = quat->w * z2; const float xz2 = quat->x * z2; const float wy2 = quat->w * y2; result->m[0] = 1.0f - yy2 - zz2; result->m[1] = xy2 + wz2; result->m[2] = xz2 - wy2; result->m[3] = 0.0f; result->m[4] = xy2 - wz2; result->m[5] = 1.0f - xx2 - zz2; result->m[6] = yz2 + wx2; result->m[7] = 0.0f; result->m[8] = xz2 + wy2; result->m[9] = yz2 - wx2; result->m[10] = 1.0f - xx2 - yy2; result->m[11] = 0.0f; result->m[12] = 0.0f; result->m[13] = 0.0f; result->m[14] = 0.0f; result->m[15] = 1.0f; } inline static void XrMatrix4x4f_CreateTranslation(XrMatrix4x4f* result, const float x, const float y, const float z) { result->m[0] = 1.0f; result->m[1] = 0.0f; result->m[2] = 0.0f; result->m[3] = 0.0f; result->m[4] = 0.0f; result->m[5] = 1.0f; result->m[6] = 0.0f; result->m[7] = 0.0f; result->m[8] = 0.0f; result->m[9] = 0.0f; result->m[10] = 1.0f; result->m[11] = 0.0f; result->m[12] = x; result->m[13] = y; result->m[14] = z; result->m[15] = 1.0f; } inline static void XrMatrix4x4f_Multiply(XrMatrix4x4f* result, const XrMatrix4x4f* a, const XrMatrix4x4f* b) { result->m[0] = a->m[0] * b->m[0] + a->m[4] * b->m[1] + a->m[8] * b->m[2] + a->m[12] * b->m[3]; result->m[1] = a->m[1] * b->m[0] + a->m[5] * b->m[1] + a->m[9] * b->m[2] + a->m[13] * b->m[3]; result->m[2] = a->m[2] * b->m[0] + a->m[6] * b->m[1] + a->m[10] * b->m[2] + a->m[14] * b->m[3]; result->m[3] = a->m[3] * b->m[0] + a->m[7] * b->m[1] + a->m[11] * b->m[2] + a->m[15] * b->m[3]; result->m[4] = a->m[0] * b->m[4] + a->m[4] * b->m[5] + a->m[8] * b->m[6] + a->m[12] * b->m[7]; result->m[5] = a->m[1] * b->m[4] + a->m[5] * b->m[5] + a->m[9] * b->m[6] + a->m[13] * b->m[7]; result->m[6] = a->m[2] * b->m[4] + a->m[6] * b->m[5] + a->m[10] * b->m[6] + a->m[14] * b->m[7]; result->m[7] = a->m[3] * b->m[4] + a->m[7] * b->m[5] + a->m[11] * b->m[6] + a->m[15] * b->m[7]; result->m[8] = a->m[0] * b->m[8] + a->m[4] * b->m[9] + a->m[8] * b->m[10] + a->m[12] * b->m[11]; result->m[9] = a->m[1] * b->m[8] + a->m[5] * b->m[9] + a->m[9] * b->m[10] + a->m[13] * b->m[11]; result->m[10] = a->m[2] * b->m[8] + a->m[6] * b->m[9] + a->m[10] * b->m[10] + a->m[14] * b->m[11]; result->m[11] = a->m[3] * b->m[8] + a->m[7] * b->m[9] + a->m[11] * b->m[10] + a->m[15] * b->m[11]; result->m[12] = a->m[0] * b->m[12] + a->m[4] * b->m[13] + a->m[8] * b->m[14] + a->m[12] * b->m[15]; result->m[13] = a->m[1] * b->m[12] + a->m[5] * b->m[13] + a->m[9] * b->m[14] + a->m[13] * b->m[15]; result->m[14] = a->m[2] * b->m[12] + a->m[6] * b->m[13] + a->m[10] * b->m[14] + a->m[14] * b->m[15]; result->m[15] = a->m[3] * b->m[12] + a->m[7] * b->m[13] + a->m[11] * b->m[14] + a->m[15] * b->m[15]; } inline static void XrMatrix4x4f_Invert(XrMatrix4x4f* result, const XrMatrix4x4f* src) { result->m[0] = src->m[0]; result->m[1] = src->m[4]; result->m[2] = src->m[8]; result->m[3] = 0.0f; result->m[4] = src->m[1]; result->m[5] = src->m[5]; result->m[6] = src->m[9]; result->m[7] = 0.0f; result->m[8] = src->m[2]; result->m[9] = src->m[6]; result->m[10] = src->m[10]; result->m[11] = 0.0f; result->m[12] = -(src->m[0] * src->m[12] + src->m[1] * src->m[13] + src->m[2] * src->m[14]); result->m[13] = -(src->m[4] * src->m[12] + src->m[5] * src->m[13] + src->m[6] * src->m[14]); result->m[14] = -(src->m[8] * src->m[12] + src->m[9] * src->m[13] + src->m[10] * src->m[14]); result->m[15] = 1.0f; } inline static void XrMatrix4x4f_CreateViewMatrix(XrMatrix4x4f* result, const XrVector3f* translation, const XrQuaternionf* rotation) { XrMatrix4x4f rotationMatrix; XrMatrix4x4f_CreateFromQuaternion(&rotationMatrix, rotation); XrMatrix4x4f translationMatrix; XrMatrix4x4f_CreateTranslation(&translationMatrix, translation->x, translation->y, translation->z); XrMatrix4x4f viewMatrix; XrMatrix4x4f_Multiply(&viewMatrix, &translationMatrix, &rotationMatrix); XrMatrix4x4f_Invert(result, &viewMatrix); } inline static void XrMatrix4x4f_CreateScale(XrMatrix4x4f* result, const float x, const float y, const float z) { result->m[0] = x; result->m[1] = 0.0f; result->m[2] = 0.0f; result->m[3] = 0.0f; result->m[4] = 0.0f; result->m[5] = y; result->m[6] = 0.0f; result->m[7] = 0.0f; result->m[8] = 0.0f; result->m[9] = 0.0f; result->m[10] = z; result->m[11] = 0.0f; result->m[12] = 0.0f; result->m[13] = 0.0f; result->m[14] = 0.0f; result->m[15] = 1.0f; } #define degrees_to_radians(angle_degrees) ((angle_degrees)*M_PI / 180.0) #define radians_to_degrees(angle_radians) ((angle_radians)*180.0 / M_PI) void render_rotated_cube( vec3_t position, float cube_size, float rotation, float* projection_matrix, int modelLoc) { mat4_t rotationmatrix = m4_rotation_y(degrees_to_radians(rotation)); mat4_t modelmatrix = m4_mul(m4_translation(position), m4_scaling(vec3(cube_size / 2., cube_size / 2., cube_size / 2.))); modelmatrix = m4_mul(modelmatrix, rotationmatrix); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, (float*)modelmatrix.m); glDrawArrays(GL_TRIANGLES, 0, 36); } void render_frame(int w, int h, GLuint shader_program_id, GLuint VAO, int view_index, XrMatrix4x4f projectionmatrix, XrMatrix4x4f viewmatrix, GLuint framebuffer, GLuint image, bool depth_supported, GLuint depthbuffer) { glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glViewport(0, 0, w, h); glScissor(0, 0, w, h); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, image, 0); if (depth_supported) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthbuffer, 0); } else { // TODO: need a depth attachment for depth test when rendering to fbo } if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { assert(0); } glClearColor(.0f, 0.0f, 0.2f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(shader_program_id); glBindVertexArray(VAO); int modelLoc = glGetUniformLocation(shader_program_id, "model"); int colorLoc = glGetUniformLocation(shader_program_id, "uniformColor"); int viewLoc = glGetUniformLocation(shader_program_id, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, (float*)viewmatrix.m); int projLoc = glGetUniformLocation(shader_program_id, "proj"); glUniformMatrix4fv(projLoc, 1, GL_FALSE, (float*)projectionmatrix.m); // render scene with 4 colorful cubes { // the special color value (0, 0, 0) will get replaced by some UV color in the shader glUniform3f(colorLoc, 0.0, 0.0, 0.0); static double display_time_seconds = 0; display_time_seconds += 0.16; const float rotations_per_sec = .25; float angle = ((long)(display_time_seconds * 360. * rotations_per_sec)) % 360; float dist = 1.5f; float height = 0.0f; render_rotated_cube(vec3(0, height, -dist), .33f, angle, projectionmatrix.m, modelLoc); render_rotated_cube(vec3(0, height, dist), .33f, angle, projectionmatrix.m, modelLoc); render_rotated_cube(vec3(dist, height, 0), .33f, angle, projectionmatrix.m, modelLoc); render_rotated_cube(vec3(-dist, height, 0), .33f, angle, projectionmatrix.m, modelLoc); } // blit left eye to desktop window glBindFramebuffer(GL_FRAMEBUFFER, 0); if (view_index == 0) { _glBlitNamedFramebuffer((GLuint)framebuffer, // readFramebuffer (GLuint)0, // backbuffer // drawFramebuffer (GLint)0, // srcX0 (GLint)0, // srcY0 (GLint)w, // srcX1 (GLint)h, // srcY1 (GLint)0, // dstX0 (GLint)0, // dstY0 (GLint)w / 2, // dstX1 (GLint)h / 2, // dstY1 (GLbitfield)GL_COLOR_BUFFER_BIT, // mask (GLenum)GL_LINEAR); // filter SDL_GL_SwapWindow(desktop_window); } } int main() { struct { // To render into a texture we need a framebuffer (one per texture to make it easy) GLuint** framebuffers; float near_z; float far_z; GLuint shader_program_id; GLuint VAO; } gl_rendering; int w = 800; int h = 600; init_sdl_window(w, h); uint32_t swapchain_lengths[] = { 3 }; init_gl(1, swapchain_lengths, &gl_rendering.framebuffers, &gl_rendering.shader_program_id, &gl_rendering.VAO); struct VK vk = {0}; init_vk(&vk); GLuint image[swapchain_lengths[0]]; #if 0 glGenTextures(swapchain_lengths[0], image); for (uint32_t i = 0; i < swapchain_lengths[0]; i++) { glBindTexture(GL_TEXTURE_2D, image[i]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGBA, GL_UNSIGNED_INT, 0); } #else for (uint32_t i = 0; i < swapchain_lengths[0]; i++) { // VK_FORMAT_R16G16B16A16_SFLOAT // GL_RGBA16F // VK_FORMAT_R8G8B8A8_SRGB // GL_RGBA if (!make_exported_texture(&vk, VK_FORMAT_R16G16B16A16_SFLOAT, GL_RGBA16F, w, h, &image[i], IMAGE_USAGE_COLOR)) { printf("failed to create exported texture\n"); return 1; } } #endif GLuint depth[swapchain_lengths[0]]; #if 1 glGenTextures(swapchain_lengths[0], depth); for (uint32_t i = 0; i < swapchain_lengths[0]; i++) { glBindTexture(GL_TEXTURE_2D, depth[i]); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, w, h, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0); } #else for (uint32_t i = 0; i < swapchain_lengths[0]; i++) { if (!make_exported_texture(&vk, VK_FORMAT_D16_UNORM, GL_DEPTH_COMPONENT16, w, h, &depth[i], IMAGE_USAGE_DEPTH)) { printf("failed to create exported texture\n"); return 1; } } #endif uint32_t swapchain_index = 0; while (true) { swapchain_index++; if (swapchain_index >= swapchain_lengths[0]) { swapchain_index = 0; } // --- Poll SDL for events so we can exit with esc SDL_Event sdl_event; while (SDL_PollEvent(&sdl_event)) { if (sdl_event.type == SDL_QUIT || (sdl_event.type == SDL_KEYDOWN && sdl_event.key.keysym.sym == SDLK_ESCAPE)) { return 1; } } XrFovf fov = { .angleDown = -50, .angleUp = 50, .angleLeft = -50, .angleRight = 50 }; XrMatrix4x4f projection_matrix; XrMatrix4x4f_CreateProjectionFov(&projection_matrix, fov, 0.001, 1000); XrVector3f translation = { .x = 0, .y = 0, .z = 0.5}; XrQuaternionf rotation = { .x = 0, .y = 0, .z = 0, .w = 1}; XrMatrix4x4f view_matrix; XrMatrix4x4f_CreateViewMatrix(&view_matrix, &translation, &rotation); render_frame(w, h, gl_rendering.shader_program_id, gl_rendering.VAO, 0, projection_matrix, view_matrix, gl_rendering.framebuffers[0][0], image[swapchain_index], true, depth[swapchain_index]); usleep(1000 * 100); } }