diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 218ac4541..513525b6b 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -28,6 +28,8 @@ add_library(common
fifo_queue.h
file_system.cpp
file_system.h
+ image.cpp
+ image.h
gl/context.cpp
gl/context.h
gl/program.cpp
@@ -91,7 +93,7 @@ add_library(common
target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
-target_link_libraries(common PRIVATE glad libcue Threads::Threads cubeb libchdr glslang vulkan-loader)
+target_link_libraries(common PRIVATE glad libcue stb Threads::Threads cubeb libchdr glslang vulkan-loader)
if(WIN32)
target_sources(common PRIVATE
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index de0c5284b..771a21083 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -62,6 +62,7 @@
+
@@ -114,6 +115,7 @@
+
@@ -300,7 +302,7 @@
WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
stdcpp17
true
@@ -326,7 +328,7 @@
_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
false
true
@@ -355,7 +357,7 @@
WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
stdcpp17
true
@@ -381,7 +383,7 @@
_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
false
true
@@ -411,7 +413,7 @@
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
true
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
stdcpp17
false
@@ -441,7 +443,7 @@
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
true
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
@@ -471,7 +473,7 @@
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
true
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
stdcpp17
false
@@ -501,7 +503,7 @@
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
true
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index a98013549..442f5ff09 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -98,6 +98,7 @@
vulkan
+
@@ -188,6 +189,7 @@
vulkan
+
diff --git a/src/common/image.cpp b/src/common/image.cpp
new file mode 100644
index 000000000..b80e10dd5
--- /dev/null
+++ b/src/common/image.cpp
@@ -0,0 +1,39 @@
+#include "image.h"
+#include "log.h"
+#include "stb_image.h"
+Log_SetChannel(Common::Image);
+
+namespace Common {
+bool LoadImageFromFile(Common::RGBA8Image* image, const char* filename)
+{
+ int width, height, file_channels;
+ u8* pixel_data = stbi_load(filename, &width, &height, &file_channels, 4);
+ if (!pixel_data)
+ {
+ const char* error_reason = stbi_failure_reason();
+ Log_ErrorPrintf("Failed to load image from '%s': %s", filename, error_reason ? error_reason : "unknown error");
+ return false;
+ }
+
+ image->SetPixels(static_cast(width), static_cast(height), reinterpret_cast(pixel_data));
+ stbi_image_free(pixel_data);
+ return true;
+}
+
+bool LoadImageFromBuffer(Common::RGBA8Image* image, const void* buffer, std::size_t buffer_size)
+{
+ int width, height, file_channels;
+ u8* pixel_data = stbi_load_from_memory(static_cast(buffer), static_cast(buffer_size), &width,
+ &height, &file_channels, 4);
+ if (!pixel_data)
+ {
+ const char* error_reason = stbi_failure_reason();
+ Log_ErrorPrintf("Failed to load image from memory: %s", error_reason ? error_reason : "unknown error");
+ return false;
+ }
+
+ image->SetPixels(static_cast(width), static_cast(height), reinterpret_cast(pixel_data));
+ stbi_image_free(pixel_data);
+ return true;
+}
+} // namespace Common
\ No newline at end of file
diff --git a/src/common/image.h b/src/common/image.h
new file mode 100644
index 000000000..6054aa0f7
--- /dev/null
+++ b/src/common/image.h
@@ -0,0 +1,98 @@
+#pragma once
+#include "assert.h"
+#include "types.h"
+#include
+#include
+#include
+#include
+
+namespace Common {
+template
+class Image
+{
+public:
+ Image() = default;
+ Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); }
+ Image(const Image& copy)
+ {
+ m_width = copy.m_width;
+ m_height = copy.m_height;
+ m_pixels = copy.m_pixels;
+ }
+ Image(Image&& move)
+ {
+ m_width = move.m_width;
+ m_height = move.m_height;
+ m_pixels = std::move(move.m_width);
+ move.m_width = 0;
+ move.m_height = 0;
+ }
+
+ Image& operator=(const Image& copy)
+ {
+ m_width = copy.m_width;
+ m_height = copy.m_height;
+ m_pixels = copy.m_pixels;
+ return *this;
+ }
+ Image& operator=(Image&& move)
+ {
+ m_width = move.m_width;
+ m_height = move.m_height;
+ m_pixels = std::move(move.m_width);
+ move.m_width = 0;
+ move.m_height = 0;
+ return *this;
+ }
+
+ ALWAYS_INLINE bool IsValid() const { return (m_width > 0 && m_height > 0); }
+ ALWAYS_INLINE u32 GetWidth() const { return m_width; }
+ ALWAYS_INLINE u32 GetHeight() const { return m_height; }
+ ALWAYS_INLINE u32 GetByteStride() const { return (sizeof(PixelType) * m_width); }
+ ALWAYS_INLINE const PixelType* GetPixels() const { return m_pixels.data(); }
+ ALWAYS_INLINE PixelType* GetPixels() { return m_pixels.data(); }
+ ALWAYS_INLINE const PixelType* GetRowPixels(u32 y) const { return &m_pixels[y * m_width]; }
+ ALWAYS_INLINE PixelType* GetRowPixels(u32 y) { return &m_pixels[y * m_width]; }
+ ALWAYS_INLINE void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; }
+ ALWAYS_INLINE PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; }
+
+ void Clear(PixelType fill_value = static_cast(0))
+ {
+ std::fill(m_pixels.begin(), m_pixels.end(), fill_value);
+ }
+
+ void Invalidate()
+ {
+ m_width = 0;
+ m_height = 0;
+ m_pixels.clear();
+ }
+
+ void SetSize(u32 new_width, u32 new_height, PixelType fill_value = static_cast(0))
+ {
+ m_width = new_width;
+ m_height = new_height;
+ m_pixels.resize(new_width * new_height);
+ Clear(fill_value);
+ }
+
+ void SetPixels(u32 width, u32 height, const PixelType* pixels)
+ {
+ m_width = width;
+ m_height = height;
+ m_pixels.resize(width * height);
+ std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType));
+ }
+
+private:
+ u32 m_width = 0;
+ u32 m_height = 0;
+ std::vector m_pixels;
+};
+
+using RGBA8Image = Image;
+
+bool LoadImageFromFile(Common::RGBA8Image* image, const char* filename);
+bool LoadImageFromBuffer(Common::RGBA8Image* image, const void* buffer, std::size_t buffer_size);
+
+} // namespace Common
\ No newline at end of file