mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 21:00:05 +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 | set(HDR_UTIL source/scwx/qt/util/font.hpp | ||||||
|              source/scwx/qt/util/font_buffer.hpp |              source/scwx/qt/util/font_buffer.hpp | ||||||
|              source/scwx/qt/util/json.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 | set(SRC_UTIL source/scwx/qt/util/font.cpp | ||||||
|              source/scwx/qt/util/font_buffer.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 | set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp | ||||||
|              source/scwx/qt/view/level3_product_view.hpp |              source/scwx/qt/view/level3_product_view.hpp | ||||||
|              source/scwx/qt/view/level3_radial_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