diff --git a/src/overlay/renderer/renderer.h b/src/overlay/renderer/renderer.h index 328269b..37528d2 100644 --- a/src/overlay/renderer/renderer.h +++ b/src/overlay/renderer/renderer.h @@ -2,6 +2,8 @@ #include "platform.h" #include +#include +#include // tell GLFW to NOT include OpenGL as it'll conflict with GLAD in the OpenGL backend #define GLFW_INCLUDE_NONE #include @@ -20,6 +22,16 @@ namespace spacecal { typedef uintptr_t RenderHandle_t; constexpr RenderHandle_t k_INVALID_RENDER_HANDLE = (RenderHandle_t)-1; + typedef uintptr_t TextureHandle_t; + constexpr TextureHandle_t k_INVALID_TEXTURE_HANDLE = (TextureHandle_t)-1; + + struct TextureData_t { + TextureHandle_t hTexture = k_INVALID_TEXTURE_HANDLE; + uint32_t dwWidth = 0; + uint32_t dwHeight = 0; + uintptr_t hInternalData = (uintptr_t)-1; // index, for vulkan + }; + class IRenderContext { public: virtual ~IRenderContext() = default; @@ -35,6 +47,10 @@ namespace spacecal { virtual void present(int width, int height) = 0; virtual vr::Texture_t getVRTexture() = 0; + + virtual TextureData_t loadTexture(const std::string& szFilePath) = 0; + inline TextureData_t loadTexture(const std::filesystem::path& path) { return loadTexture(path.string()); } + virtual void destroyTexture(TextureData_t hTexture) = 0; }; bool isGraphicsApiSupported(GraphicsBackend gfxApi); diff --git a/src/overlay/renderer/renderer_dx11.cpp b/src/overlay/renderer/renderer_dx11.cpp index d2d6e8d..956c58e 100644 --- a/src/overlay/renderer/renderer_dx11.cpp +++ b/src/overlay/renderer/renderer_dx11.cpp @@ -12,6 +12,7 @@ #include #include "log.h" #include "util.h" +#include namespace spacecal { namespace renderer { @@ -223,6 +224,63 @@ namespace spacecal { SAFE_RELEASE(m_mainRenderTargetView); SAFE_RELEASE(m_pBackBuffer); } + + TextureData_t Renderer_DX11::loadTexture(const std::string& szFilePath) { + ID3D11ShaderResourceView* pSrv = nullptr; + + int image_width, image_height, nrChannels; + unsigned char* textureData = stbi_load(szFilePath.c_str(), &image_width, &image_height, &nrChannels, STBI_rgb_alpha); + if (textureData) { + D3D11_TEXTURE2D_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Width = image_width; + desc.Height = image_height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = 0; + + ID3D11Texture2D* pTexture = NULL; + D3D11_SUBRESOURCE_DATA subResource; + subResource.pSysMem = textureData; + subResource.SysMemPitch = desc.Width * 4; + subResource.SysMemSlicePitch = 0; + m_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); + + // Create texture view + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + m_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &pSrv); + SAFE_RELEASE(pTexture); + stbi_image_free(textureData); + } else { + LOG_WARN("Failed to load texture from {}...", szFilePath); + } + + TextureHandle_t hHandle = pSrv == nullptr ? k_INVALID_TEXTURE_HANDLE : (TextureHandle_t)pSrv; + TextureData_t data = { + .hTexture = hHandle, + .dwWidth = (uint32_t) image_width, + .dwHeight = (uint32_t) image_height, + .hInternalData = (uintptr_t)-1, + }; + + return data; + } + + void Renderer_DX11::destroyTexture(TextureData_t hTexture) { + if (hTexture.hTexture != k_INVALID_TEXTURE_HANDLE) { + ID3D11ShaderResourceView* pSrv = (ID3D11ShaderResourceView*) hTexture.hTexture; + SAFE_RELEASE(pSrv); + } + } } } diff --git a/src/overlay/renderer/renderer_dx11.h b/src/overlay/renderer/renderer_dx11.h index 2558312..04a1388 100644 --- a/src/overlay/renderer/renderer_dx11.h +++ b/src/overlay/renderer/renderer_dx11.h @@ -27,6 +27,9 @@ namespace spacecal { void present(int width, int height) override; vr::Texture_t getVRTexture() override; + + TextureData_t loadTexture(const std::string& szFilePath) override; + void destroyTexture(TextureData_t hTexture) override; private: IDXGIAdapter* get_dxgi_adapter_by_index_or_luid(int32_t adapterIndex, uint64_t dwLuid); bool create_d3d_device(HWND hWnd); diff --git a/src/overlay/renderer/renderer_opengl.cpp b/src/overlay/renderer/renderer_opengl.cpp index f8169dd..fdcbe1a 100644 --- a/src/overlay/renderer/renderer_opengl.cpp +++ b/src/overlay/renderer/renderer_opengl.cpp @@ -2,6 +2,7 @@ #include #include "log.h" #include "util.h" +#include namespace spacecal { namespace renderer { @@ -122,5 +123,42 @@ namespace spacecal { }; return vrTexture; } + + TextureData_t Renderer_OpenGL::loadTexture(const std::string& szFilePath) { + GLuint hGlTexture = 0; + glGenTextures(1, &hGlTexture); + glBindTexture(GL_TEXTURE_2D, hGlTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + int width, height, nrChannels; + unsigned char* textureData = stbi_load(szFilePath.c_str(), &width, &height, &nrChannels, STBI_rgb_alpha); + if (textureData) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData); + glGenerateMipmap(GL_TEXTURE_2D); + stbi_image_free(textureData); + } else { + LOG_WARN("OpenGL Failed to load texture from {}...", szFilePath); + glDeleteTextures(1, &hGlTexture); + hGlTexture = 0; + } + TextureHandle_t hHandle = hGlTexture == 0 ? k_INVALID_TEXTURE_HANDLE : (TextureHandle_t)hGlTexture; + TextureData_t data = { + .hTexture = hHandle, + .dwWidth = (uint32_t) width, + .dwHeight = (uint32_t)height, + .hInternalData = (uintptr_t)-1, + }; + + return data; + } + + void Renderer_OpenGL::destroyTexture(TextureData_t hTexture) { + if (hTexture.hTexture != k_INVALID_TEXTURE_HANDLE) { + GLuint hGlTexture = (GLuint) hTexture.hTexture; + glDeleteTextures(1, &hGlTexture); + } + } } } \ No newline at end of file diff --git a/src/overlay/renderer/renderer_opengl.h b/src/overlay/renderer/renderer_opengl.h index 6b201b2..bb84d22 100644 --- a/src/overlay/renderer/renderer_opengl.h +++ b/src/overlay/renderer/renderer_opengl.h @@ -20,6 +20,9 @@ namespace spacecal { void present(int width, int height) override; vr::Texture_t getVRTexture() override; + + TextureData_t loadTexture(const std::string& szFilePath) override; + void destroyTexture(TextureData_t hTexture) override; private: GLFWwindow* m_window = nullptr; GLuint m_fboHandle = 0; diff --git a/src/overlay/renderer/renderer_vulkan.cpp b/src/overlay/renderer/renderer_vulkan.cpp index b230a35..c20f814 100644 --- a/src/overlay/renderer/renderer_vulkan.cpp +++ b/src/overlay/renderer/renderer_vulkan.cpp @@ -4,6 +4,7 @@ #include #include "log.h" #include "util.h" +#include namespace spacecal { namespace renderer { @@ -156,7 +157,8 @@ namespace spacecal { instance_extensions.push_back(openvr_instance_extensions[i].c_str()); } } - + + m_loadedTextureData.reserve(64); return setupVulkan(instance_extensions); } @@ -537,5 +539,219 @@ namespace spacecal { return true; } + + uint32_t Renderer_Vulkan::findMemoryType(uint32_t type_filter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties mem_properties; + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &mem_properties); + + for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) + if ((type_filter & (1 << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) + return i; + + return 0xFFFFFFFF; // Unable to find memoryType + } + + TextureData_t Renderer_Vulkan::loadTexture(const std::string& szFilePath) { + int width, height, nrChannels; + uintptr_t dwInternalDataIdx = (uintptr_t) -1; + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + + unsigned char* textureData = stbi_load(szFilePath.c_str(), &width, &height, &nrChannels, STBI_rgb_alpha); + if (textureData) { + size_t image_size = width * height * nrChannels; + VkResult err; + + VkTextureData_t tex_data = {}; + + // Create the Vulkan image. + { + VkImageCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.extent.width = width; + info.extent.height = height; + info.extent.depth = 1; + info.mipLevels = 1; + info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + err = vkCreateImage(m_device, &info, m_allocator, &tex_data.Image); + check_vk_result(err); + VkMemoryRequirements req; + vkGetImageMemoryRequirements(m_device, tex_data.Image, &req); + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = findMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + err = vkAllocateMemory(m_device, &alloc_info, m_allocator, &tex_data.ImageMemory); + check_vk_result(err); + err = vkBindImageMemory(m_device, tex_data.Image, tex_data.ImageMemory, 0); + check_vk_result(err); + } + + // Create the Image View + { + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = tex_data.Image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + info.subresourceRange.levelCount = 1; + info.subresourceRange.layerCount = 1; + err = vkCreateImageView(m_device, &info, m_allocator, &tex_data.ImageView); + check_vk_result(err); + } + + // Create Image View Descriptor Set + // (note: before 1.92.8 this also took a Sampler. See Wiki history) + descriptorSet = ImGui_ImplVulkan_AddTexture(tex_data.ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // Create Upload Buffer + { + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = image_size; + buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + err = vkCreateBuffer(m_device, &buffer_info, m_allocator, &tex_data.UploadBuffer); + check_vk_result(err); + VkMemoryRequirements req; + vkGetBufferMemoryRequirements(m_device, tex_data.UploadBuffer, &req); + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = findMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + err = vkAllocateMemory(m_device, &alloc_info, m_allocator, &tex_data.UploadBufferMemory); + check_vk_result(err); + err = vkBindBufferMemory(m_device, tex_data.UploadBuffer, tex_data.UploadBufferMemory, 0); + check_vk_result(err); + } + + // Upload to Buffer: + { + void* map = NULL; + err = vkMapMemory(m_device, tex_data.UploadBufferMemory, 0, image_size, 0, &map); + check_vk_result(err); + memcpy(map, textureData, image_size); + VkMappedMemoryRange range[1] = {}; + range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[0].memory = tex_data.UploadBufferMemory; + range[0].size = image_size; + err = vkFlushMappedMemoryRanges(m_device, 1, range); + check_vk_result(err); + vkUnmapMemory(m_device, tex_data.UploadBufferMemory); + } + + // Release image memory using stb + stbi_image_free(textureData); + + // Create a command buffer that will perform following steps when hit in the command queue. + // TODO: this works in the example, but may need input if this is an acceptable way to access the pool/create the command buffer. + VkCommandPool command_pool = m_mainWindowData.Frames[m_mainWindowData.FrameIndex].CommandPool; + VkCommandBuffer command_buffer; + { + VkCommandBufferAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandPool = command_pool; + alloc_info.commandBufferCount = 1; + + err = vkAllocateCommandBuffers(m_device, &alloc_info, &command_buffer); + check_vk_result(err); + + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + err = vkBeginCommandBuffer(command_buffer, &begin_info); + check_vk_result(err); + } + + // Copy to Image + { + VkImageMemoryBarrier copy_barrier[1] = {}; + copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].image = tex_data.Image; + copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy_barrier[0].subresourceRange.levelCount = 1; + copy_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier); + + VkBufferImageCopy region = {}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = width; + region.imageExtent.height = height; + region.imageExtent.depth = 1; + vkCmdCopyBufferToImage(command_buffer, tex_data.UploadBuffer, tex_data.Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + VkImageMemoryBarrier use_barrier[1] = {}; + use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].image = tex_data.Image; + use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + use_barrier[0].subresourceRange.levelCount = 1; + use_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier); + } + + // End command buffer + { + VkSubmitInfo end_info = {}; + end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + end_info.commandBufferCount = 1; + end_info.pCommandBuffers = &command_buffer; + err = vkEndCommandBuffer(command_buffer); + check_vk_result(err); + err = vkQueueSubmit(m_queue, 1, &end_info, VK_NULL_HANDLE); + check_vk_result(err); + err = vkDeviceWaitIdle(m_device); + check_vk_result(err); + } + + dwInternalDataIdx = m_loadedTextureData.size(); + m_loadedTextureData.push_back(tex_data); + } else { + LOG_WARN("Failed to load texture from {}...", szFilePath); + } + + TextureData_t data = { + .hTexture = descriptorSet == VK_NULL_HANDLE ? k_INVALID_TEXTURE_HANDLE : (TextureHandle_t) descriptorSet, + .dwWidth = (uint32_t)width, + .dwHeight = (uint32_t)height, + .hInternalData = dwInternalDataIdx, + }; + return data; + } + + void Renderer_Vulkan::destroyTexture(TextureData_t hTexture) { + if (hTexture.hInternalData != (uintptr_t) -1) { + VkTextureData_t& tex_data = m_loadedTextureData[hTexture.hInternalData]; + + vkFreeMemory(m_device, tex_data.UploadBufferMemory, nullptr); + vkDestroyBuffer(m_device, tex_data.UploadBuffer, nullptr); + vkDestroyImageView(m_device, tex_data.ImageView, nullptr); + vkDestroyImage(m_device, tex_data.Image, nullptr); + vkFreeMemory(m_device, tex_data.ImageMemory, nullptr); + } + if (hTexture.hTexture != k_INVALID_TEXTURE_HANDLE) { + VkDescriptorSet descriptorSet = (VkDescriptorSet)hTexture.hTexture; + ImGui_ImplVulkan_RemoveTexture(descriptorSet); + } + } } } \ No newline at end of file diff --git a/src/overlay/renderer/renderer_vulkan.h b/src/overlay/renderer/renderer_vulkan.h index 02577fb..d86c409 100644 --- a/src/overlay/renderer/renderer_vulkan.h +++ b/src/overlay/renderer/renderer_vulkan.h @@ -25,11 +25,15 @@ namespace spacecal { void present(int width, int height) override; vr::Texture_t getVRTexture() override; + + TextureData_t loadTexture(const std::string& szFilePath) override; + void destroyTexture(TextureData_t hTexture) override; private: bool setupVulkan(ImVector instance_extensions); bool getOpenvrVulkanInstanceExtensionsRequired(std::vector& outInstanceExtensionList); bool getOpenvrVulkanDeviceExtensionsRequired(VkPhysicalDevice pPhysicalDevice, std::vector& outDeviceExtensionList); + uint32_t findMemoryType(uint32_t type_filter, VkMemoryPropertyFlags properties); private: GLFWwindow* m_window = nullptr; int m_width = 0; @@ -50,6 +54,18 @@ namespace spacecal { bool m_swapChainRebuild = false; vr::VRVulkanTextureData_t m_vrTextureData = {}; + + // auxiliary data + struct VkTextureData_t { + // VkDescriptorSet DS; --> this is TextureHandle_t + VkImageView ImageView = VK_NULL_HANDLE; + VkImage Image = VK_NULL_HANDLE; + VkDeviceMemory ImageMemory = VK_NULL_HANDLE; + VkBuffer UploadBuffer = VK_NULL_HANDLE; + VkDeviceMemory UploadBufferMemory = VK_NULL_HANDLE; + }; + + std::vector m_loadedTextureData; }; } } \ No newline at end of file