diff options
author | rtk0c <[email protected]> | 2022-04-17 20:08:57 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2022-04-17 20:08:57 -0700 |
commit | 5424a1d5434e3ddd911a504719918c2df027e2fd (patch) | |
tree | 6275aab13140d81dcc46c8290e73ac9a8bbb5605 /source/Texture.cpp | |
parent | afcac59c7d04f4337d6b04ebed8cac7e871ccc50 (diff) |
Changeset: 8 Initial work on sprites and texture system
Diffstat (limited to 'source/Texture.cpp')
-rw-r--r-- | source/Texture.cpp | 251 |
1 files changed, 193 insertions, 58 deletions
diff --git a/source/Texture.cpp b/source/Texture.cpp index 08641e1..8fd74ac 100644 --- a/source/Texture.cpp +++ b/source/Texture.cpp @@ -1,90 +1,220 @@ #include "Texture.hpp" +#include "Macros.hpp" #include "PodVector.hpp" +#include "ScopeGuard.hpp" #include <stb_image.h> #include <stb_rect_pack.h> -#include <cstdint> +#include <bit> #include <cstring> -#include <deque> -#include <filesystem> -#include <fstream> -#include <stdexcept> #include <utility> Texture::~Texture() { glDeleteTextures(1, &mHandle); } -static GLenum MapTextureFilteringToGL(Texture::Filtering option) { +static GLenum MapTextureFilteringToGL(Tags::TexFilter option) { + using namespace Tags; switch (option) { - case Texture::LinearFilter: return GL_LINEAR; - case Texture::NearestFilter: return GL_NEAREST; + case TF_Linear: return GL_LINEAR; + case TF_Nearest: return GL_NEAREST; } return 0; } -bool Texture::InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically) { +Texture::ErrorCode Texture::InitFromFile(const char* filePath) { + if (IsValid()) { + return EC_AlreadyInitialized; + } + int width, height; int channels; - stbi_set_flip_vertically_on_load(flipVertically); auto result = (uint8_t*)stbi_load(filePath, &width, &height, &channels, 4); if (!result) { - return false; + return EC_FileIoFailed; } + DEFER { stbi_image_free(result); }; - glDeleteTextures(1, &mHandle); // In case the caller gave us glGenTextures(1, &mHandle); glBindTexture(GL_TEXTURE_2D, mHandle); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ::MapTextureFilteringToGL(props.minifyingFilter)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ::MapTextureFilteringToGL(props.magnifyingFilter)); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result); mInfo.size = { width, height }; - mInfo.isAtlas = false; - return true; + + return EC_Success; } -// bool Texture::InitFromImage(const Image& image, const TextureProperties& props, bool flipVertically) { -// GLenum sourceFormat; -// switch (image.GetChannels()) { -// case 1: sourceFormat = GL_RED; break; -// case 2: sourceFormat = GL_RG; break; -// case 3: sourceFormat = GL_RGB; break; -// case 4: sourceFormat = GL_RGBA; break; -// default: return false; -// } +Texture::ErrorCode Texture::InitFromImage(const Image& image) { + if (IsValid()) { + return EC_AlreadyInitialized; + } + + GLenum sourceFormat; + switch (image.GetChannels()) { + case 1: sourceFormat = GL_RED; break; + case 2: sourceFormat = GL_RG; break; + case 3: sourceFormat = GL_RGB; break; + case 4: sourceFormat = GL_RGBA; break; + default: return EC_InvalidImage; + } + + auto size = image.GetSize(); + uint8_t* dataPtr = image.GetDataPtr(); + + glGenTextures(1, &mHandle); + glBindTexture(GL_TEXTURE_2D, mHandle); -// auto size = image.GetSize(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.x, size.y, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr); -// std::unique_ptr<uint8_t[]> dataStorage; -// uint8_t* dataPtr; -// if (flipVertically) { -// dataStorage = std::make_unique<uint8_t[]>(image.GetDataLength()); -// dataPtr = dataStorage.get(); + mInfo.size = size; -// size_t rowStride = size.width * image.GetChannels() * sizeof(uint8_t); -// for (size_t y = 0; y < size.height; ++y) { -// size_t invY = (size.height - 1) - y; -// std::memcpy(dataPtr + invY * rowStride, image.GetDataPtr() + y * rowStride, rowStride); -// } -// } else { -// // dataStorage is unused, we read pixels directly from `image` -// dataPtr = image.GetDataPtr(); -// } + return EC_Success; +} -// glDeleteTextures(1, &mHandle); -// glGenTextures(1, &mHandle); -// glBindTexture(GL_TEXTURE_2D, mHandle); -// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ::MapTextureFilteringToGL(props.minifyingFilter)); -// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ::MapTextureFilteringToGL(props.magnifyingFilter)); -// glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.width, size.height, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr); +Texture::ErrorCode Texture::InitAtlas(const AtlasInput& in, AtlasOutput* out) { + // Force RGBA for easier time uploading to GL texture + constexpr int kDesiredChannels = 4; -// mInfo.size = size; -// mInfo.isAtlas = false; -// return true; -// } + PodVector<stbrp_rect> rects; + rects.resize(in.sources.size()); + + for (size_t i = 0; i < in.sources.size(); ++i) { + auto size = in.sources[i].image.GetSize(); + auto& rect = rects[i]; + rect.w = static_cast<stbrp_coord>(size.x); + rect.h = static_cast<stbrp_coord>(size.y); + } + + int atlasWidth; + int atlasHeight; + + // 1. Pack the candidate rectanges onto the (not yet allocated) atlas + // Note that the coordinates here are top-left origin + switch (in.packingMode) { + case PM_KeepSquare: { + atlasWidth = 512; + atlasHeight = 512; + + PodVector<stbrp_node> nodes; + while (true) { + // No need to zero initialize stbrp_node, library will take care of that + nodes.resize(atlasWidth); + + stbrp_context ctx; + stbrp_init_target(&ctx, atlasWidth, atlasHeight, &nodes[0], (int)nodes.size()); + int result = stbrp_pack_rects(&ctx, rects.data(), (int)rects.size()); + + if (result != 1) { + atlasWidth *= 2; + atlasHeight *= 2; + } else { + // Break out of the while loop + break; + } + } + } break; + + case PM_VerticalExtension: + case PM_HorizontalExtension: { + constexpr int kMaxHeight = 1024 * 32; + atlasWidth = 0; + atlasHeight = 0; + + PodVector<stbrp_node> nodes; + stbrp_context ctx; + stbrp_init_target(&ctx, atlasWidth, atlasHeight, &nodes[0], nodes.size()); + stbrp_pack_rects(&ctx, rects.data(), rects.size()); + + // Calculate width/height needed for atlas + auto& limiter = in.packingMode == PM_VerticalExtension ? atlasHeight : atlasWidth; + for (auto& rect : rects) { + int bottom = rect.y + rect.h; + limiter = std::max(limiter, bottom); + } + limiter = std::bit_ceil<uint32_t>(limiter); + } break; + } + + // 2. Allocate atlas bitmap + + // Number of bytes in *bitmap* + auto bytes = atlasWidth * atlasHeight * kDesiredChannels * sizeof(uint8_t); + // Note that the origin (first pixel) is the bottom-left corner, to be consistent with OpenGL + auto bitmap = std::make_unique<uint8_t[]>(bytes); + std::memset(bitmap.get(), 0, bytes * sizeof(uint8_t)); + + // 3. Put all candidate images to the atlas bitmap + // TODO don't flip + // We essentially flip the candidate images vertically when putting into the atlas bitmap, so that when OpenGL reads + // these bytes, it sees the "bottom row" (if talking in top-left origin) first + // (empty spots are set with 0, "flipping" doesn't apply to them) + // + // Conceptually, we flip the atlas bitmap vertically so that the origin is at bottom-left + // i.e. all the coordinates we talk (e.g. rect.x/y) are still in top-left origin + + // Unit: bytes + size_t bitmapRowStride = atlasWidth * kDesiredChannels * sizeof(uint8_t); + for (size_t i = 0; i < in.sources.size(); ++i) { + auto& rect = rects[i]; + // Data is assumed to be stored in top-left origin + auto data = in.sources[i].image.GetDataPtr(); + + // We need to copy row by row, because the candidate image bytes won't land in a continuous chunk in our atlas bitmap + // Unit: bytes + size_t incomingRowStride = rect.w * kDesiredChannels * sizeof(uint8_t); + // Unit: bytes + size_t bitmapX = rect.x * kDesiredChannels * sizeof(uint8_t); + for (int y = 0; y < rect.h; ++y) { + auto src = data + y * incomingRowStride; + + int bitmapY = y; + auto dst = bitmap.get() + bitmapY * bitmapRowStride + bitmapX; + + std::memcpy(dst, src, incomingRowStride); + } + } + + // 4. Upload to VRAM + GLuint atlasTexture; + glGenTextures(1, &atlasTexture); + glBindTexture(GL_TEXTURE_2D, atlasTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, atlasWidth, atlasHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap.get()); + + // 5. Generate atlas texture info + mHandle = atlasTexture; + mInfo.size = { atlasWidth, atlasHeight }; + + // 6. Generate output information + if (out) { + out->elements.reserve(in.sources.size()); + for (size_t i = 0; i < in.sources.size(); ++i) { + auto& rect = rects[i]; + auto& source = in.sources[i]; + out->elements.push_back(AltasElement{ + .name = source.name, + .subregion = Subregion{ + .u0 = (float)(rect.x) / atlasWidth, + .v0 = (float)(rect.y + rect.h) / atlasHeight, + .u1 = (float)(rect.x + rect.w) / atlasWidth, + .v1 = (float)(rect.y) / atlasHeight, + }, + .subregionSize = glm::ivec2(rect.w, rect.h), + }); + } + } + + return EC_Success; +} const TextureInfo& Texture::GetInfo() const { return mInfo; @@ -98,15 +228,20 @@ bool Texture::IsValid() const { return mHandle != 0; } -void TextureManager::DiscoverTextures() { - // TODO +Texture* IresTexture::CreateInstance() const { } -Texture* TextureManager::FindTexture(std::string_view name) { - auto iter = mTextures.find(name); - if (iter != mTextures.end()) { - return iter->second.Get(); - } else { - return nullptr; +Texture* IresTexture::GetInstance() { + if (mInstance == nullptr) { + mInstance.Attach(CreateInstance()); } + return mInstance.Get(); +} + +void IresTexture::Write(rapidjson::Value& value, rapidjson::Document& root) const { + // TODO +} + +void IresTexture::Read(const rapidjson::Value& value) { + // TODO } |