From 2f8e78b24794c8327f7328ac34afd5355f1d0ec5 Mon Sep 17 00:00:00 2001 From: StillHammer Date: Thu, 27 Nov 2025 19:44:13 +0800 Subject: [PATCH] feat(BgfxRenderer): Phase 6 - Texture loading with stb_image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add TextureLoader class using stb_image for PNG/JPG/etc loading - Integrate TextureLoader with ResourceCache for cached texture loading - Add SpritePass::setTexture() for binding textures to sprites - Add "defaultTexture" config option to load texture at startup - Create assets/textures folder structure - Add 1f440.png (eyes emoji) test texture Visual test confirms textured sprites render correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- assets/textures/1f440.png | Bin 0 -> 34548 bytes modules/BgfxRenderer/BgfxRendererModule.cpp | 19 ++++- modules/BgfxRenderer/BgfxRendererModule.h | 4 + modules/BgfxRenderer/CMakeLists.txt | 2 + modules/BgfxRenderer/Passes/SpritePass.cpp | 5 +- modules/BgfxRenderer/Passes/SpritePass.h | 9 +++ .../BgfxRenderer/Resources/ResourceCache.cpp | 25 +++---- .../BgfxRenderer/Resources/TextureLoader.cpp | 70 ++++++++++++++++++ .../BgfxRenderer/Resources/TextureLoader.h | 45 +++++++++++ tests/visual/test_23_bgfx_sprites_visual.cpp | 14 ++-- 10 files changed, 165 insertions(+), 28 deletions(-) create mode 100644 assets/textures/1f440.png create mode 100644 modules/BgfxRenderer/Resources/TextureLoader.cpp create mode 100644 modules/BgfxRenderer/Resources/TextureLoader.h diff --git a/assets/textures/1f440.png b/assets/textures/1f440.png new file mode 100644 index 0000000000000000000000000000000000000000..624fca8b2e6b51886a6887101172082a1deedb69 GIT binary patch literal 34548 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 Bq=Wzf literal 0 HcmV?d00001 diff --git a/modules/BgfxRenderer/BgfxRendererModule.cpp b/modules/BgfxRenderer/BgfxRendererModule.cpp index 6a03e48..d209007 100644 --- a/modules/BgfxRenderer/BgfxRendererModule.cpp +++ b/modules/BgfxRenderer/BgfxRendererModule.cpp @@ -82,8 +82,13 @@ void BgfxRendererModule::setConfiguration(const IDataNode& config, IIO* io, ITas m_renderGraph = std::make_unique(); m_renderGraph->addPass(std::make_unique()); m_logger->info("Added ClearPass"); - m_renderGraph->addPass(std::make_unique(spriteShader)); + + // Create SpritePass and keep reference for texture binding + auto spritePass = std::make_unique(spriteShader); + m_spritePass = spritePass.get(); // Non-owning reference + m_renderGraph->addPass(std::move(spritePass)); m_logger->info("Added SpritePass"); + m_renderGraph->addPass(std::make_unique(debugShader)); m_logger->info("Added DebugPass"); m_renderGraph->setup(*m_device); @@ -99,6 +104,18 @@ void BgfxRendererModule::setConfiguration(const IDataNode& config, IIO* io, ITas // Setup resource cache m_resourceCache = std::make_unique(); + // Load default texture if specified in config + std::string defaultTexturePath = config.getString("defaultTexture", ""); + if (!defaultTexturePath.empty()) { + rhi::TextureHandle tex = m_resourceCache->loadTexture(*m_device, defaultTexturePath); + if (tex.isValid()) { + m_spritePass->setTexture(tex); + m_logger->info("Loaded default texture: {}", defaultTexturePath); + } else { + m_logger->warn("Failed to load default texture: {}", defaultTexturePath); + } + } + m_logger->info("BgfxRenderer initialized successfully"); } diff --git a/modules/BgfxRenderer/BgfxRendererModule.h b/modules/BgfxRenderer/BgfxRendererModule.h index 95ce1b0..6feb140 100644 --- a/modules/BgfxRenderer/BgfxRendererModule.h +++ b/modules/BgfxRenderer/BgfxRendererModule.h @@ -16,6 +16,7 @@ class RenderGraph; class SceneCollector; class ResourceCache; class ShaderManager; +class SpritePass; // ============================================================================ // BgfxRenderer Module - 2D rendering via bgfx @@ -55,6 +56,9 @@ private: std::unique_ptr m_sceneCollector; std::unique_ptr m_resourceCache; + // Pass references (non-owning, owned by RenderGraph) + SpritePass* m_spritePass = nullptr; + // IIO (non-owning) IIO* m_io = nullptr; diff --git a/modules/BgfxRenderer/CMakeLists.txt b/modules/BgfxRenderer/CMakeLists.txt index 06cead2..814e797 100644 --- a/modules/BgfxRenderer/CMakeLists.txt +++ b/modules/BgfxRenderer/CMakeLists.txt @@ -57,11 +57,13 @@ add_library(BgfxRenderer SHARED # Resources Resources/ResourceCache.cpp + Resources/TextureLoader.cpp ) target_include_directories(BgfxRenderer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../include + ${bgfx_SOURCE_DIR}/bimg/3rdparty # stb_image ) target_link_libraries(BgfxRenderer PRIVATE diff --git a/modules/BgfxRenderer/Passes/SpritePass.cpp b/modules/BgfxRenderer/Passes/SpritePass.cpp index 77c8f45..73d1cfc 100644 --- a/modules/BgfxRenderer/Passes/SpritePass.cpp +++ b/modules/BgfxRenderer/Passes/SpritePass.cpp @@ -104,8 +104,9 @@ void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi: cmd.setIndexBuffer(m_quadIB); cmd.setInstanceBuffer(m_instanceBuffer, 0, static_cast(batchSize)); - // Bind default texture (TODO: support per-sprite textures via texture array) - cmd.setTexture(0, m_defaultTexture, m_textureSampler); + // Bind texture (use active texture if set, otherwise default white) + rhi::TextureHandle texToUse = m_activeTexture.isValid() ? m_activeTexture : m_defaultTexture; + cmd.setTexture(0, texToUse, m_textureSampler); // Submit draw call cmd.drawInstanced(6, static_cast(batchSize)); // 6 indices per quad diff --git a/modules/BgfxRenderer/Passes/SpritePass.h b/modules/BgfxRenderer/Passes/SpritePass.h index ac40cee..adc88c9 100644 --- a/modules/BgfxRenderer/Passes/SpritePass.h +++ b/modules/BgfxRenderer/Passes/SpritePass.h @@ -25,6 +25,14 @@ public: void shutdown(rhi::IRHIDevice& device) override; void execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd) override; + /** + * @brief Set a texture to use for all sprites (temporary API) + * @param texture The texture handle to use (must be valid) + * + * TODO: Replace with proper texture array / per-sprite texture support + */ + void setTexture(rhi::TextureHandle texture) { m_activeTexture = texture; } + private: rhi::ShaderHandle m_shader; rhi::BufferHandle m_quadVB; @@ -32,6 +40,7 @@ private: rhi::BufferHandle m_instanceBuffer; rhi::UniformHandle m_textureSampler; rhi::TextureHandle m_defaultTexture; // White 1x1 texture fallback + rhi::TextureHandle m_activeTexture; // Currently active texture (if set) static constexpr uint32_t MAX_SPRITES_PER_BATCH = 10000; }; diff --git a/modules/BgfxRenderer/Resources/ResourceCache.cpp b/modules/BgfxRenderer/Resources/ResourceCache.cpp index 5df72df..c4c93b1 100644 --- a/modules/BgfxRenderer/Resources/ResourceCache.cpp +++ b/modules/BgfxRenderer/Resources/ResourceCache.cpp @@ -1,4 +1,5 @@ #include "ResourceCache.h" +#include "TextureLoader.h" #include "../RHI/RHIDevice.h" #include #include @@ -33,29 +34,21 @@ rhi::TextureHandle ResourceCache::loadTexture(rhi::IRHIDevice& device, const std } } - // Load texture data from file - // TODO: Use stb_image or similar to load actual texture files - // For now, create a placeholder 1x1 white texture + // Load texture from file using TextureLoader (stb_image) + auto result = TextureLoader::loadFromFile(device, path); - uint32_t whitePixel = 0xFFFFFFFF; - - rhi::TextureDesc desc; - desc.width = 1; - desc.height = 1; - desc.mipLevels = 1; - desc.format = rhi::TextureDesc::RGBA8; - desc.data = &whitePixel; - desc.dataSize = sizeof(whitePixel); - - rhi::TextureHandle handle = device.createTexture(desc); + if (!result.success) { + // Return invalid handle on failure + return rhi::TextureHandle{}; + } // Store in cache { std::unique_lock lock(m_mutex); - m_textures[path] = handle; + m_textures[path] = result.handle; } - return handle; + return result.handle; } rhi::ShaderHandle ResourceCache::loadShader(rhi::IRHIDevice& device, const std::string& name, diff --git a/modules/BgfxRenderer/Resources/TextureLoader.cpp b/modules/BgfxRenderer/Resources/TextureLoader.cpp new file mode 100644 index 0000000..5d4fa02 --- /dev/null +++ b/modules/BgfxRenderer/Resources/TextureLoader.cpp @@ -0,0 +1,70 @@ +#include "TextureLoader.h" +#include "../RHI/RHIDevice.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#include + +namespace grove { + +TextureLoader::LoadResult TextureLoader::loadFromFile(rhi::IRHIDevice& device, const std::string& path) { + LoadResult result; + + // Read file into memory + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + result.error = "Failed to open file: " + path; + return result; + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector buffer(static_cast(size)); + if (!file.read(reinterpret_cast(buffer.data()), size)) { + result.error = "Failed to read file: " + path; + return result; + } + + return loadFromMemory(device, buffer.data(), buffer.size()); +} + +TextureLoader::LoadResult TextureLoader::loadFromMemory(rhi::IRHIDevice& device, const uint8_t* data, size_t size) { + LoadResult result; + + // Decode image with stb_image + int width, height, channels; + // Force RGBA output (4 channels) + stbi_uc* pixels = stbi_load_from_memory(data, static_cast(size), &width, &height, &channels, 4); + + if (!pixels) { + result.error = "stb_image failed: "; + result.error += stbi_failure_reason(); + return result; + } + + // Create texture via RHI + rhi::TextureDesc desc; + desc.width = static_cast(width); + desc.height = static_cast(height); + desc.format = rhi::TextureDesc::RGBA8; + desc.data = pixels; + desc.dataSize = static_cast(width * height * 4); + + result.handle = device.createTexture(desc); + result.width = desc.width; + result.height = desc.height; + result.success = result.handle.isValid(); + + if (!result.success) { + result.error = "Failed to create GPU texture"; + } + + // Free stb_image memory + stbi_image_free(pixels); + + return result; +} + +} // namespace grove diff --git a/modules/BgfxRenderer/Resources/TextureLoader.h b/modules/BgfxRenderer/Resources/TextureLoader.h new file mode 100644 index 0000000..e91b614 --- /dev/null +++ b/modules/BgfxRenderer/Resources/TextureLoader.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../RHI/RHITypes.h" +#include +#include +#include + +namespace grove { + +namespace rhi { class IRHIDevice; } + +/** + * @brief Loads textures from files (PNG, JPG, etc.) + * + * Uses stb_image for decoding. All textures are loaded as RGBA8. + */ +class TextureLoader { +public: + struct LoadResult { + bool success = false; + rhi::TextureHandle handle; + uint16_t width = 0; + uint16_t height = 0; + std::string error; + }; + + /** + * @brief Load texture from file + * @param device RHI device for texture creation + * @param path Path to image file (PNG, JPG, BMP, TGA, etc.) + * @return LoadResult with handle and dimensions on success + */ + static LoadResult loadFromFile(rhi::IRHIDevice& device, const std::string& path); + + /** + * @brief Load texture from memory + * @param device RHI device for texture creation + * @param data Raw file data (PNG/JPG bytes, not decoded pixels) + * @param size Size of data in bytes + * @return LoadResult with handle and dimensions on success + */ + static LoadResult loadFromMemory(rhi::IRHIDevice& device, const uint8_t* data, size_t size); +}; + +} // namespace grove diff --git a/tests/visual/test_23_bgfx_sprites_visual.cpp b/tests/visual/test_23_bgfx_sprites_visual.cpp index 43bf215..1846fa7 100644 --- a/tests/visual/test_23_bgfx_sprites_visual.cpp +++ b/tests/visual/test_23_bgfx_sprites_visual.cpp @@ -120,6 +120,9 @@ int main(int argc, char* argv[]) { config.setDouble("nativeWindowHandle", static_cast(reinterpret_cast(nativeWindowHandle))); config.setDouble("nativeDisplayHandle", static_cast(reinterpret_cast(nativeDisplayHandle))); + // Load texture from assets folder + config.setString("defaultTexture", "../../assets/textures/1f440.png"); + module->setConfiguration(config, rendererIO.get(), nullptr); std::cout << "Module configured\n"; @@ -208,15 +211,8 @@ int main(int argc, char* argv[]) { sprite->setDouble("u1", 1.0); sprite->setDouble("v1", 1.0); - // Different colors for each sprite (ABGR format for bgfx) - uint32_t colors[] = { - 0xFF0000FF, // Red - 0xFF00FF00, // Green - 0xFFFF0000, // Blue - 0xFF00FFFF, // Yellow - 0xFFFF00FF // Magenta - }; - sprite->setInt("color", static_cast(colors[i])); + // All sprites white to show texture without tint + sprite->setInt("color", static_cast(0xFFFFFFFF)); sprite->setInt("textureId", 0); sprite->setInt("layer", i);