mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 00:00:04 +00:00 
			
		
		
		
	Initial texture atlas creation implementation
This commit is contained in:
		
							parent
							
								
									031e175fed
								
							
						
					
					
						commit
						17192470ec
					
				
					 3 changed files with 313 additions and 2 deletions
				
			
		|  | @ -108,10 +108,12 @@ set(SRC_UI source/scwx/qt/ui/flow_layout.cpp | |||
| set(HDR_UTIL source/scwx/qt/util/font.hpp | ||||
|              source/scwx/qt/util/font_buffer.hpp | ||||
|              source/scwx/qt/util/json.hpp | ||||
|              source/scwx/qt/util/streams.hpp) | ||||
|              source/scwx/qt/util/streams.hpp | ||||
|              source/scwx/qt/util/texture_atlas.hpp) | ||||
| set(SRC_UTIL source/scwx/qt/util/font.cpp | ||||
|              source/scwx/qt/util/font_buffer.cpp | ||||
|              source/scwx/qt/util/json.cpp) | ||||
|              source/scwx/qt/util/json.cpp | ||||
|              source/scwx/qt/util/texture_atlas.cpp) | ||||
| set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp | ||||
|              source/scwx/qt/view/level3_product_view.hpp | ||||
|              source/scwx/qt/view/level3_radial_view.hpp | ||||
|  |  | |||
							
								
								
									
										268
									
								
								scwx-qt/source/scwx/qt/util/texture_atlas.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								scwx-qt/source/scwx/qt/util/texture_atlas.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,268 @@ | |||
| #include <scwx/qt/util/texture_atlas.hpp> | ||||
| #include <scwx/qt/util/streams.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <shared_mutex> | ||||
| #include <unordered_map> | ||||
| 
 | ||||
| #pragma warning(push, 0) | ||||
| #pragma warning(disable : 4714) | ||||
| #include <boost/gil/extension/io/png.hpp> | ||||
| #include <boost/iostreams/stream.hpp> | ||||
| #include <stb_rect_pack.h> | ||||
| #include <QFile> | ||||
| #pragma warning(pop) | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace util | ||||
| { | ||||
| 
 | ||||
| static const std::string logPrefix_ = "scwx::qt::util::texture_atlas"; | ||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||
| 
 | ||||
| struct TextureInfo | ||||
| { | ||||
|    TextureInfo(boost::gil::point_t position, boost::gil::point_t size) : | ||||
|        position_ {position}, size_ {size} | ||||
|    { | ||||
|    } | ||||
| 
 | ||||
|    boost::gil::point_t position_; | ||||
|    boost::gil::point_t size_; | ||||
| }; | ||||
| 
 | ||||
| class TextureAtlas::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Impl() : | ||||
|        texturePathMap_ {}, | ||||
|        texturePathMutex_ {}, | ||||
|        atlas_ {}, | ||||
|        atlasMap_ {}, | ||||
|        atlasMutex_ {} | ||||
|    { | ||||
|    } | ||||
|    ~Impl() {} | ||||
| 
 | ||||
|    static boost::gil::rgba8_image_t LoadImage(const std::string& imagePath); | ||||
| 
 | ||||
|    std::unordered_map<std::string, std::string> texturePathMap_; | ||||
|    std::shared_mutex                            texturePathMutex_; | ||||
| 
 | ||||
|    boost::gil::rgba8_image_t                    atlas_; | ||||
|    std::unordered_map<std::string, TextureInfo> atlasMap_; | ||||
|    std::shared_mutex                            atlasMutex_; | ||||
| }; | ||||
| 
 | ||||
| TextureAtlas::TextureAtlas() : p(std::make_unique<Impl>()) {} | ||||
| TextureAtlas::~TextureAtlas() = default; | ||||
| 
 | ||||
| TextureAtlas::TextureAtlas(TextureAtlas&&) noexcept            = default; | ||||
| TextureAtlas& TextureAtlas::operator=(TextureAtlas&&) noexcept = default; | ||||
| 
 | ||||
| void TextureAtlas::RegisterTexture(const std::string& name, | ||||
|                                    const std::string& path) | ||||
| { | ||||
|    std::unique_lock lock(p->texturePathMutex_); | ||||
|    p->texturePathMap_.insert_or_assign(name, path); | ||||
| } | ||||
| 
 | ||||
| void TextureAtlas::BuildAtlas(size_t width, size_t height) | ||||
| { | ||||
|    logger_->debug("Building {}x{} texture atlas", width, height); | ||||
| 
 | ||||
|    if (width > INT_MAX || height > INT_MAX) | ||||
|    { | ||||
|       logger_->error("Cannot build texture atlas of size {}x{}", width, height); | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    std::vector<std::pair<std::string, boost::gil::rgba8_image_t>> images; | ||||
|    std::vector<stbrp_rect>                                        stbrpRects; | ||||
| 
 | ||||
|    // Load images
 | ||||
|    { | ||||
|       // Take a read lock on the texture path map
 | ||||
|       std::shared_lock lock(p->texturePathMutex_); | ||||
| 
 | ||||
|       // For each registered texture
 | ||||
|       std::for_each(p->texturePathMap_.cbegin(), | ||||
|                     p->texturePathMap_.cend(), | ||||
|                     [&](const auto& pair) | ||||
|                     { | ||||
|                        // Load texture image
 | ||||
|                        boost::gil::rgba8_image_t image = | ||||
|                           Impl::LoadImage(pair.second); | ||||
| 
 | ||||
|                        if (image.width() > 0u && image.height() > 0u) | ||||
|                        { | ||||
|                           // Store STB rectangle pack data in a vector
 | ||||
|                           stbrpRects.push_back(stbrp_rect { | ||||
|                              0, | ||||
|                              static_cast<stbrp_coord>(image.width()), | ||||
|                              static_cast<stbrp_coord>(image.height()), | ||||
|                              0, | ||||
|                              0, | ||||
|                              0}); | ||||
| 
 | ||||
|                           // Store image data in a vector
 | ||||
|                           images.emplace_back(pair.first, std::move(image)); | ||||
|                        } | ||||
|                     }); | ||||
|    } | ||||
| 
 | ||||
|    // Pack images
 | ||||
|    { | ||||
|       logger_->trace("Packing {} images", images.size()); | ||||
| 
 | ||||
|       // Optimal number of nodes = width
 | ||||
|       stbrp_context           stbrpContext; | ||||
|       std::vector<stbrp_node> stbrpNodes(width); | ||||
| 
 | ||||
|       stbrp_init_target(&stbrpContext, | ||||
|                         static_cast<int>(width), | ||||
|                         static_cast<int>(height), | ||||
|                         stbrpNodes.data(), | ||||
|                         static_cast<int>(stbrpNodes.size())); | ||||
| 
 | ||||
|       // Pack loaded textures
 | ||||
|       stbrp_pack_rects( | ||||
|          &stbrpContext, stbrpRects.data(), static_cast<int>(stbrpRects.size())); | ||||
|    } | ||||
| 
 | ||||
|    // Lock atlas
 | ||||
|    std::unique_lock lock(p->atlasMutex_); | ||||
| 
 | ||||
|    // Clear index
 | ||||
|    p->atlasMap_.clear(); | ||||
| 
 | ||||
|    // Clear atlas
 | ||||
|    p->atlas_.recreate(width, height); | ||||
|    boost::gil::rgba8_view_t atlasView = boost::gil::view(p->atlas_); | ||||
| 
 | ||||
|    // Populate atlas
 | ||||
|    logger_->trace("Populating atlas"); | ||||
| 
 | ||||
|    for (size_t i = 0; i < images.size(); i++) | ||||
|    { | ||||
|       // If the image was packed successfully
 | ||||
|       if (stbrpRects[i].was_packed != 0) | ||||
|       { | ||||
|          // Populate the atlas
 | ||||
|          boost::gil::rgba8c_view_t imageView = | ||||
|             boost::gil::const_view(images[i].second); | ||||
|          boost::gil::rgba8_view_t atlasSubView = | ||||
|             boost::gil::subimage_view(atlasView, | ||||
|                                       stbrpRects[i].x, | ||||
|                                       stbrpRects[i].y, | ||||
|                                       imageView.width(), | ||||
|                                       imageView.height()); | ||||
| 
 | ||||
|          boost::gil::copy_pixels(imageView, atlasSubView); | ||||
| 
 | ||||
|          // Add texture image to the index
 | ||||
|          p->atlasMap_.emplace( | ||||
|             std::piecewise_construct, | ||||
|             std::forward_as_tuple(images[i].first), | ||||
|             std::forward_as_tuple( | ||||
|                boost::gil::point_t {stbrpRects[i].x, stbrpRects[i].y}, | ||||
|                boost::gil::point_t {imageView.width(), imageView.height()})); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          logger_->warn("Unable to pack texture: {}", images[i].first); | ||||
|       } | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) | ||||
| { | ||||
|    GLuint texture = GL_INVALID_INDEX; | ||||
| 
 | ||||
|    std::shared_lock lock(p->atlasMutex_); | ||||
| 
 | ||||
|    if (p->atlas_.width() > 0u && p->atlas_.height() > 0u) | ||||
|    { | ||||
|       boost::gil::rgba8_view_t               view = boost::gil::view(p->atlas_); | ||||
|       std::vector<boost::gil::rgba8_pixel_t> pixelData(view.width() * | ||||
|                                                        view.height()); | ||||
| 
 | ||||
|       boost::gil::copy_pixels( | ||||
|          view, | ||||
|          boost::gil::interleaved_view(view.width(), | ||||
|                                       view.height(), | ||||
|                                       pixelData.data(), | ||||
|                                       view.width() * | ||||
|                                          sizeof(boost::gil::rgba8_pixel_t))); | ||||
| 
 | ||||
|       lock.unlock(); | ||||
| 
 | ||||
|       gl.glGenTextures(1, &texture); | ||||
|       gl.glBindTexture(GL_TEXTURE_2D, texture); | ||||
| 
 | ||||
|       gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||||
|       gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||||
|       gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||
|       gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||
| 
 | ||||
|       gl.glTexImage2D(GL_TEXTURE_2D, | ||||
|                       0, | ||||
|                       GL_RGBA, | ||||
|                       view.width(), | ||||
|                       view.height(), | ||||
|                       0, | ||||
|                       GL_RGBA, | ||||
|                       GL_UNSIGNED_BYTE, | ||||
|                       pixelData.data()); | ||||
|    } | ||||
| 
 | ||||
|    return texture; | ||||
| } | ||||
| 
 | ||||
| boost::gil::rgba8_image_t | ||||
| TextureAtlas::Impl::LoadImage(const std::string& imagePath) | ||||
| { | ||||
|    logger_->debug("Loading image: {}", imagePath); | ||||
| 
 | ||||
|    boost::gil::rgba8_image_t image; | ||||
| 
 | ||||
|    QFile imageFile(imagePath.c_str()); | ||||
| 
 | ||||
|    imageFile.open(QIODevice::ReadOnly); | ||||
| 
 | ||||
|    if (!imageFile.isOpen()) | ||||
|    { | ||||
|       logger_->error("Could not open image: {}", imagePath); | ||||
|       return std::move(image); | ||||
|    } | ||||
| 
 | ||||
|    boost::iostreams::stream<util::IoDeviceSource> dataStream(imageFile); | ||||
| 
 | ||||
|    boost::gil::image<boost::gil::rgba8_pixel_t, false> x; | ||||
| 
 | ||||
|    try | ||||
|    { | ||||
|       boost::gil::read_and_convert_image( | ||||
|          dataStream, image, boost::gil::png_tag()); | ||||
|    } | ||||
|    catch (const std::exception& ex) | ||||
|    { | ||||
|       logger_->error("Error reading image: {}", ex.what()); | ||||
|       return std::move(image); | ||||
|    } | ||||
| 
 | ||||
|    return std::move(image); | ||||
| } | ||||
| 
 | ||||
| TextureAtlas& TextureAtlas::Instance() | ||||
| { | ||||
|    static TextureAtlas instance_ {}; | ||||
|    return instance_; | ||||
| } | ||||
| 
 | ||||
| } // namespace util
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										41
									
								
								scwx-qt/source/scwx/qt/util/texture_atlas.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								scwx-qt/source/scwx/qt/util/texture_atlas.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/qt/gl/gl.hpp> | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace util | ||||
| { | ||||
| 
 | ||||
| class TextureAtlas | ||||
| { | ||||
| public: | ||||
|    explicit TextureAtlas(); | ||||
|    ~TextureAtlas(); | ||||
| 
 | ||||
|    TextureAtlas(const TextureAtlas&)            = delete; | ||||
|    TextureAtlas& operator=(const TextureAtlas&) = delete; | ||||
| 
 | ||||
|    TextureAtlas(TextureAtlas&&) noexcept; | ||||
|    TextureAtlas& operator=(TextureAtlas&&) noexcept; | ||||
| 
 | ||||
|    static TextureAtlas& Instance(); | ||||
| 
 | ||||
|    void   RegisterTexture(const std::string& name, const std::string& path); | ||||
|    void   BuildAtlas(size_t width, size_t height); | ||||
|    GLuint BufferAtlas(gl::OpenGLFunctions& gl); | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
| 
 | ||||
|    std::unique_ptr<Impl> p; | ||||
| }; | ||||
| 
 | ||||
| } // namespace util
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat