feat: add support for loading images

This commit is contained in:
Hyblocker committed 2026-08-14 12:38:19 +02:00
1 parent 1a8e656737
commit eabd71b217
7 files changed
+351 -1

No files matched your search

+16
View File
@@ -2,6 +2,8 @@
#include "platform.h"
#include <inttypes.h>
#include <string>
#include <filesystem>
// tell GLFW to NOT include OpenGL as it'll conflict with GLAD in the OpenGL backend
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
@@ -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);
+58
View File
@@ -12,6 +12,7 @@
#include <imgui/backends/imgui_impl_dx11.h>
#include "log.h"
#include "util.h"
#include <stb_image.h>
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);
}
}
}
}
+3
View File
@@ -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);
+38
View File
@@ -2,6 +2,7 @@
#include <imgui/backends/imgui_impl_opengl3.h>
#include "log.h"
#include "util.h"
#include <stb_image.h>
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);
}
}
}
}
+3
View File
@@ -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;
+217 -1
View File
@@ -4,6 +4,7 @@
#include <volk.h>
#include "log.h"
#include "util.h"
#include <stb_image.h>
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, &region);
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);
}
}
}
}
+16
View File
@@ -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<const char*> instance_extensions);
bool getOpenvrVulkanInstanceExtensionsRequired(std::vector<std::string>& outInstanceExtensionList);
bool getOpenvrVulkanDeviceExtensionsRequired(VkPhysicalDevice pPhysicalDevice, std::vector<std::string>& 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<VkTextureData_t> m_loadedTextureData;
};
}
}