Added texture packer for automatically creating texture atlas on build.
This commit is contained in:
parent
b29fefa6ec
commit
cf4d4cb8a6
@ -5,6 +5,9 @@ config = {
|
|||||||
env = SConscript('external/scons-plus-plus/SConscript', exports = ['config'])
|
env = SConscript('external/scons-plus-plus/SConscript', exports = ['config'])
|
||||||
env.Append(CPPPATH = [Dir('private'), Dir('public')])
|
env.Append(CPPPATH = [Dir('private'), Dir('public')])
|
||||||
|
|
||||||
|
# texture packer
|
||||||
|
env = env.Module('private/texture_packer/SModule')
|
||||||
|
|
||||||
# app
|
# app
|
||||||
env = env.Module('private/sdl_gpu_test/SModule')
|
env = env.Module('private/sdl_gpu_test/SModule')
|
||||||
|
|
||||||
|
3
assets/bitmaps/.gitignore
vendored
Normal file
3
assets/bitmaps/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# these are auto-generated
|
||||||
|
ui.png
|
||||||
|
ui.txt
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
2
external/scons-plus-plus
vendored
2
external/scons-plus-plus
vendored
@ -1 +1 @@
|
|||||||
Subproject commit b45fec75610a05ecf1e01b6b27807e20be5cd94b
|
Subproject commit a7c736de56b438d2e47b074fc1b06d05cd7a0c35
|
@ -11,6 +11,7 @@ src_files = Split("""
|
|||||||
util/bitmap.cpp
|
util/bitmap.cpp
|
||||||
util/font_map.cpp
|
util/font_map.cpp
|
||||||
util/mesh.cpp
|
util/mesh.cpp
|
||||||
|
util/texture_atlas.cpp
|
||||||
|
|
||||||
0_clear_swapchain/app.cpp
|
0_clear_swapchain/app.cpp
|
||||||
1_green_triangle/app.cpp
|
1_green_triangle/app.cpp
|
||||||
@ -48,4 +49,16 @@ for shader_file in shader_files:
|
|||||||
spv = env.SpirV(source = shader_file, target=f'{shader_file}.spv')
|
spv = env.SpirV(source = shader_file, target=f'{shader_file}.spv')
|
||||||
env.Default(spv)
|
env.Default(spv)
|
||||||
|
|
||||||
|
ui_textures = Split("""
|
||||||
|
#assets/fonts/symtex.png
|
||||||
|
#assets/bitmaps/cube.png
|
||||||
|
#assets/bitmaps/sdl.png
|
||||||
|
""")
|
||||||
|
|
||||||
|
pack = env.PackTextures(
|
||||||
|
target = '#assets/bitmaps/ui.png',
|
||||||
|
source = ui_textures
|
||||||
|
)
|
||||||
|
env.Default(pack)
|
||||||
|
|
||||||
Return('env')
|
Return('env')
|
||||||
|
@ -84,14 +84,6 @@ void UIRenderer::init(Application& application)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// load the font map for rendering text
|
|
||||||
const FontMap fontMap = loadFontMap(application.getFileSystem().getPath("fonts/symtext.fnt"));
|
|
||||||
mFontMap = makeUVFontMap({
|
|
||||||
.original = &fontMap,
|
|
||||||
.textureWidth = 256,
|
|
||||||
.textureHeight = 256
|
|
||||||
});
|
|
||||||
|
|
||||||
// create texture and sampler
|
// create texture and sampler
|
||||||
sdlpp::GPUTextureCreateArgs textureArgs = {
|
sdlpp::GPUTextureCreateArgs textureArgs = {
|
||||||
.format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB,
|
.format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB,
|
||||||
@ -99,6 +91,30 @@ void UIRenderer::init(Application& application)
|
|||||||
};
|
};
|
||||||
mTexture = application.loadTexture("bitmaps/ui.png", textureArgs);
|
mTexture = application.loadTexture("bitmaps/ui.png", textureArgs);
|
||||||
|
|
||||||
|
// load the texture atlas
|
||||||
|
const TextureAtlas textureAtlas = loadTextureAtlas(application.getFileSystem().getPath("bitmaps/ui.txt"));
|
||||||
|
mTextureAtlas = makeUVTextureAtlas({
|
||||||
|
.original = &textureAtlas,
|
||||||
|
.textureWidth = textureArgs.width,
|
||||||
|
.textureHeight = textureArgs.height
|
||||||
|
});
|
||||||
|
|
||||||
|
auto itEntry = mTextureAtlas.entries.find("assets/fonts/symtex.png");
|
||||||
|
if (itEntry == mTextureAtlas.entries.end())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Texture atlas is missing entry for the font.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the font map for rendering text
|
||||||
|
const FontMap fontMap = loadFontMap(application.getFileSystem().getPath("fonts/symtext.fnt"));
|
||||||
|
mFontMap = makeUVFontMap({
|
||||||
|
.original = &fontMap,
|
||||||
|
.textureWidth = textureArgs.width,
|
||||||
|
.textureHeight = textureArgs.height,
|
||||||
|
.textureOffsetX = itEntry->second.uvX,
|
||||||
|
.textureOffsetY = itEntry->second.uvY
|
||||||
|
});
|
||||||
|
|
||||||
mSampler.create(device, {});
|
mSampler.create(device, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "../application.hpp"
|
#include "../application.hpp"
|
||||||
#include "../sdlpp/gpu.hpp"
|
#include "../sdlpp/gpu.hpp"
|
||||||
#include "../util/font_map.hpp"
|
#include "../util/font_map.hpp"
|
||||||
|
#include "../util/texture_atlas.hpp"
|
||||||
|
|
||||||
namespace sdl_gpu_test::inline app6
|
namespace sdl_gpu_test::inline app6
|
||||||
{
|
{
|
||||||
@ -58,6 +59,7 @@ private:
|
|||||||
std::vector<UIVertex> mVertices;
|
std::vector<UIVertex> mVertices;
|
||||||
std::vector<primitive_id_t> mTriangleOwners;
|
std::vector<primitive_id_t> mTriangleOwners;
|
||||||
primitive_id_t mNextOwner = 1;
|
primitive_id_t mNextOwner = 1;
|
||||||
|
UVTextureAtlas mTextureAtlas;
|
||||||
UVFontMap mFontMap;
|
UVFontMap mFontMap;
|
||||||
bool mVerticesDirty = true;
|
bool mVerticesDirty = true;
|
||||||
std::size_t mVertexBufferSize = 0;
|
std::size_t mVertexBufferSize = 0;
|
||||||
|
@ -72,7 +72,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
catch (std::runtime_error& error)
|
catch (std::runtime_error& error)
|
||||||
{
|
{
|
||||||
spdlog::error("{}.", error.what());
|
spdlog::error("{}", error.what());
|
||||||
return USER_ERROR;
|
return USER_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,8 +75,8 @@ UVFontMap makeUVFontMap(const MakeUVFontMapArgs& args)
|
|||||||
{
|
{
|
||||||
const FontMapEntry& origEntry = args.original->entries[chr];
|
const FontMapEntry& origEntry = args.original->entries[chr];
|
||||||
result.entries[chr] = {
|
result.entries[chr] = {
|
||||||
.uvX = static_cast<float>(origEntry.x + args.textureOffsetX) / static_cast<float>(args.textureWidth),
|
.uvX = static_cast<float>(origEntry.x) / static_cast<float>(args.textureWidth) + args.textureOffsetX,
|
||||||
.uvY = static_cast<float>(origEntry.y + args.textureOffsetX) / static_cast<float>(args.textureHeight),
|
.uvY = static_cast<float>(origEntry.y) / static_cast<float>(args.textureHeight) + args.textureOffsetY,
|
||||||
.uvWidth = static_cast<float>(origEntry.width) / static_cast<float>(args.textureWidth),
|
.uvWidth = static_cast<float>(origEntry.width) / static_cast<float>(args.textureWidth),
|
||||||
.uvHeight = static_cast<float>(origEntry.height) / static_cast<float>(args.textureHeight),
|
.uvHeight = static_cast<float>(origEntry.height) / static_cast<float>(args.textureHeight),
|
||||||
.width = origEntry.width,
|
.width = origEntry.width,
|
||||||
|
@ -53,8 +53,8 @@ struct MakeUVFontMapArgs
|
|||||||
const FontMap* original;
|
const FontMap* original;
|
||||||
unsigned textureWidth;
|
unsigned textureWidth;
|
||||||
unsigned textureHeight;
|
unsigned textureHeight;
|
||||||
unsigned textureOffsetX = 0;
|
float textureOffsetX = 0.f;
|
||||||
unsigned textureOffsetY = 0;
|
float textureOffsetY = 0.f;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
|
53
private/sdl_gpu_test/util/texture_atlas.cpp
Normal file
53
private/sdl_gpu_test/util/texture_atlas.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
#include "./texture_atlas.hpp"
|
||||||
|
|
||||||
|
#include <mijin/util/string.hpp>
|
||||||
|
|
||||||
|
namespace sdl_gpu_test
|
||||||
|
{
|
||||||
|
TextureAtlas loadTextureAtlas(const mijin::PathReference& path)
|
||||||
|
{
|
||||||
|
std::unique_ptr<mijin::Stream> stream;
|
||||||
|
mijin::throwOnError(path.open(mijin::FileOpenMode::READ, stream));
|
||||||
|
|
||||||
|
auto parseNumber = [](std::string_view value, auto& outNumber)
|
||||||
|
{
|
||||||
|
if (!mijin::toNumber(value, outNumber))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Invalid number.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TextureAtlas result;
|
||||||
|
std::string line;
|
||||||
|
while (!stream->isAtEnd())
|
||||||
|
{
|
||||||
|
mijin::throwOnError(stream->readLine(line));
|
||||||
|
const auto [name, x, y, width, height] = mijin::splitFixed<5>(line, " ");
|
||||||
|
TextureAtlasEntry entry;
|
||||||
|
parseNumber(x, entry.x);
|
||||||
|
parseNumber(y, entry.y);
|
||||||
|
parseNumber(width, entry.width);
|
||||||
|
parseNumber(height, entry.height);
|
||||||
|
result.entries.emplace(name, entry);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
UVTextureAtlas makeUVTextureAtlas(const MakeUVTextureAtlasArgs& args)
|
||||||
|
{
|
||||||
|
UVTextureAtlas result;
|
||||||
|
for (const auto& [name, origEntry] : args.original->entries)
|
||||||
|
{
|
||||||
|
result.entries.emplace(name, UVTextureAtlasEntry{
|
||||||
|
.uvX = static_cast<float>(origEntry.x) / static_cast<float>(args.textureWidth),
|
||||||
|
.uvY = static_cast<float>(origEntry.y) / static_cast<float>(args.textureHeight),
|
||||||
|
.uvWidth = static_cast<float>(origEntry.width) / static_cast<float>(args.textureWidth),
|
||||||
|
.uvHeight = static_cast<float>(origEntry.height) / static_cast<float>(args.textureHeight),
|
||||||
|
.width = origEntry.width,
|
||||||
|
.height = origEntry.height
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,53 @@
|
|||||||
#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_TEXTURE_ATLAS_HPP_INCLUDED)
|
#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_TEXTURE_ATLAS_HPP_INCLUDED)
|
||||||
#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_TEXTURE_ATLAS_HPP_INCLUDED 1
|
#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_TEXTURE_ATLAS_HPP_INCLUDED 1
|
||||||
|
|
||||||
#include <vector>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <mijin/virtual_filesystem/filesystem.hpp>
|
||||||
|
|
||||||
namespace sdl_gpu_test
|
namespace sdl_gpu_test
|
||||||
{
|
{
|
||||||
|
struct TextureAtlasEntry
|
||||||
|
{
|
||||||
|
unsigned x = 0;
|
||||||
|
unsigned y = 0;
|
||||||
|
unsigned width = 0;
|
||||||
|
unsigned height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextureAtlas
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, TextureAtlasEntry> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UVTextureAtlasEntry
|
||||||
|
{
|
||||||
|
float uvX = 0.f;
|
||||||
|
float uvY = 0.f;
|
||||||
|
float uvWidth = 0.f;
|
||||||
|
float uvHeight = 0.f;
|
||||||
|
unsigned width = 0;
|
||||||
|
unsigned height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UVTextureAtlas
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, UVTextureAtlasEntry> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MakeUVTextureAtlasArgs
|
||||||
|
{
|
||||||
|
const TextureAtlas* original;
|
||||||
|
unsigned textureWidth;
|
||||||
|
unsigned textureHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TextureAtlas loadTextureAtlas(const mijin::PathReference& path);
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
UVTextureAtlas makeUVTextureAtlas(const MakeUVTextureAtlasArgs& args);
|
||||||
} // namespace sdl_gpu_test
|
} // namespace sdl_gpu_test
|
||||||
|
|
||||||
#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_TEXTURE_ATLAS_HPP_INCLUDED)
|
#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_TEXTURE_ATLAS_HPP_INCLUDED)
|
||||||
|
40
private/texture_packer/SModule
Normal file
40
private/texture_packer/SModule
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
Import('env')
|
||||||
|
|
||||||
|
def _exe_name() -> str:
|
||||||
|
if os.name == 'nt':
|
||||||
|
return env['BIN_DIR'] + '/texture_packer.exe'
|
||||||
|
return env['BIN_DIR'] + '/texture_packer'
|
||||||
|
|
||||||
|
src_files = Split("""
|
||||||
|
main.cpp
|
||||||
|
|
||||||
|
packer.cpp
|
||||||
|
""")
|
||||||
|
|
||||||
|
prog_packer = env.UnityProgram(
|
||||||
|
target = env['BIN_DIR'] + '/texture_packer',
|
||||||
|
source = src_files,
|
||||||
|
dependencies = {
|
||||||
|
'argparse': {},
|
||||||
|
'mijin': {},
|
||||||
|
'rectpack2D': {},
|
||||||
|
'spdlog': {},
|
||||||
|
'stb': {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
env.Default(prog_packer)
|
||||||
|
|
||||||
|
def _pack_textures(env, target: str, source: 'list[str]'):
|
||||||
|
cmd = env.Command(
|
||||||
|
target = target,
|
||||||
|
source = source,
|
||||||
|
action = f'{_exe_name()} $TARGET $SOURCES'
|
||||||
|
)
|
||||||
|
# env.Depends(cmd, prog_packer)
|
||||||
|
return cmd
|
||||||
|
env.AddMethod(_pack_textures, 'PackTextures')
|
||||||
|
|
||||||
|
Return('env')
|
54
private/texture_packer/main.cpp
Normal file
54
private/texture_packer/main.cpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <argparse/argparse.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <mijin/util/winundef.hpp>
|
||||||
|
|
||||||
|
#include "./packer.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
inline constexpr int APP_ERROR = 1;
|
||||||
|
inline constexpr int USER_ERROR = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
std::string outputFile;
|
||||||
|
std::vector<std::string> inputFiles;
|
||||||
|
argparse::ArgumentParser parser("texture_packer");
|
||||||
|
parser.add_argument("output_file")
|
||||||
|
.store_into(outputFile);
|
||||||
|
parser.add_argument("input_file")
|
||||||
|
.store_into(inputFiles)
|
||||||
|
.nargs(argparse::nargs_pattern::at_least_one);
|
||||||
|
// now parse
|
||||||
|
try
|
||||||
|
{
|
||||||
|
parser.parse_args(argc, argv);
|
||||||
|
}
|
||||||
|
catch (std::runtime_error& error)
|
||||||
|
{
|
||||||
|
spdlog::error("{}", error.what());
|
||||||
|
return USER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sdl_gpu_test::Packer packer;
|
||||||
|
for (const std::string& inputFile : inputFiles)
|
||||||
|
{
|
||||||
|
packer.addImage(inputFile);
|
||||||
|
}
|
||||||
|
packer.pack(outputFile);
|
||||||
|
}
|
||||||
|
catch(const std::runtime_error& error)
|
||||||
|
{
|
||||||
|
spdlog::error("{}", error.what());
|
||||||
|
return USER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
120
private/texture_packer/packer.cpp
Normal file
120
private/texture_packer/packer.cpp
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
#include "./packer.hpp"
|
||||||
|
|
||||||
|
#include <finders_interface.h>
|
||||||
|
#include <mijin/io/stream.hpp>
|
||||||
|
#include <mijin/util/iterators.hpp>
|
||||||
|
|
||||||
|
#define STBI_ASSERT(x) MIJIN_ASSERT(x, #x)
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include <stb_image.h>
|
||||||
|
#undef STB_IMAGE_IMPLEMENTATION
|
||||||
|
#undef STBI_ASSERT
|
||||||
|
|
||||||
|
#define STBIW_ASSERT(x) MIJIN_ASSERT(x, #x)
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include <stb_image_write.h>
|
||||||
|
#undef STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#undef STBIW_ASSERT
|
||||||
|
|
||||||
|
namespace sdl_gpu_test
|
||||||
|
{
|
||||||
|
void Packer::addImage(const fs::path& path)
|
||||||
|
{
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
int numComponents = 0;
|
||||||
|
stbi_uc* pixels = stbi_load(
|
||||||
|
/* filename = */ path.generic_string().c_str(),
|
||||||
|
/* x = */ &width,
|
||||||
|
/* y = */ &height,
|
||||||
|
/* channels_in_file = */ &numComponents,
|
||||||
|
/* desired_channels = */ 4
|
||||||
|
);
|
||||||
|
if (pixels == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Error loading image.");
|
||||||
|
}
|
||||||
|
|
||||||
|
mImages.push_back({
|
||||||
|
.name = path.generic_string(),
|
||||||
|
.data = std::unique_ptr<Pixel[], FreeDeleter>(reinterpret_cast<Pixel*>(pixels)),
|
||||||
|
.width = static_cast<unsigned>(width),
|
||||||
|
.height = static_cast<unsigned>(height)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Packer::pack(const fs::path& outputPath)
|
||||||
|
{
|
||||||
|
static constexpr bool allowFlip = false; // TODO: this could work?
|
||||||
|
static constexpr rectpack2D::flipping_option flippingOption = rectpack2D::flipping_option::DISABLED;
|
||||||
|
using spaces_type_t = rectpack2D::empty_spaces<allowFlip, rectpack2D::default_empty_spaces>;
|
||||||
|
using rect_type_t = rectpack2D::output_rect_t<spaces_type_t>;
|
||||||
|
|
||||||
|
auto reportSuccessful = [&](rect_type_t&)
|
||||||
|
{
|
||||||
|
return rectpack2D::callback_result::CONTINUE_PACKING;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto reportUnsuccessful = [&](rect_type_t&)-> rectpack2D::callback_result
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Could not fit images.");
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr int maxSide = 4096;
|
||||||
|
static constexpr int discardStep = -4;
|
||||||
|
|
||||||
|
std::vector<rect_type_t> rectangles;
|
||||||
|
rectangles.reserve(mImages.size());
|
||||||
|
|
||||||
|
for (const ImageData& imageData : mImages)
|
||||||
|
{
|
||||||
|
rectangles.emplace_back(0, 0, imageData.width, imageData.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rectpack2D::rect_wh resultSize = rectpack2D::find_best_packing<spaces_type_t>(
|
||||||
|
rectangles,
|
||||||
|
rectpack2D::make_finder_input(
|
||||||
|
maxSide, discardStep, reportSuccessful, reportUnsuccessful, flippingOption
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<Pixel> resultPixels;
|
||||||
|
resultPixels.resize(resultSize.area());
|
||||||
|
|
||||||
|
for (const auto& [imageData, rect] : mijin::zip(mImages, rectangles))
|
||||||
|
{
|
||||||
|
for (unsigned row = 0; row < imageData.height; ++row)
|
||||||
|
{
|
||||||
|
const Pixel* src = &imageData.data[row * imageData.width];
|
||||||
|
Pixel* dst = &resultPixels[(rect.y + row) * resultSize.w + rect.x];
|
||||||
|
std::copy_n(src, imageData.width, dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int writeResult = stbi_write_png(
|
||||||
|
/* filename = */ outputPath.generic_string().c_str(),
|
||||||
|
/* x = */ resultSize.w,
|
||||||
|
/* y = */ resultSize.h,
|
||||||
|
/* comp = */ 4,
|
||||||
|
/* data = */ resultPixels.data(),
|
||||||
|
/* stride_bytes = */ resultSize.w * sizeof(Pixel)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!writeResult)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Error writing result image.");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path atlasPath(outputPath);
|
||||||
|
atlasPath.replace_extension("txt");
|
||||||
|
mijin::FileStream fileStream;
|
||||||
|
mijin::throwOnError(fileStream.open(atlasPath.generic_string(), mijin::FileOpenMode::WRITE));
|
||||||
|
for (const auto& [imageData, rect] : mijin::zip(mImages, rectangles))
|
||||||
|
{
|
||||||
|
mijin::throwOnError(fileStream.writeText(std::format("{} {} {} {} {}\n",
|
||||||
|
imageData.name, rect.x, rect.y, rect.w, rect.h)));
|
||||||
|
}
|
||||||
|
fileStream.close();
|
||||||
|
}
|
||||||
|
}
|
47
private/texture_packer/packer.hpp
Normal file
47
private/texture_packer/packer.hpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(SDL_GPU_TEST_PRIVATE_TEXTURE_PACKER_PACKER_HPP_INCLUDED)
|
||||||
|
#define SDL_GPU_TEST_PRIVATE_TEXTURE_PACKER_PACKER_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace sdl_gpu_test
|
||||||
|
{
|
||||||
|
struct FreeDeleter
|
||||||
|
{
|
||||||
|
void operator()(void* ptr) noexcept
|
||||||
|
{
|
||||||
|
std::free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Packer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct Pixel
|
||||||
|
{
|
||||||
|
std::uint8_t r;
|
||||||
|
std::uint8_t g;
|
||||||
|
std::uint8_t b;
|
||||||
|
std::uint8_t a;
|
||||||
|
};
|
||||||
|
struct ImageData
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::unique_ptr<Pixel[], FreeDeleter> data;
|
||||||
|
unsigned width;
|
||||||
|
unsigned height;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ImageData> mImages;
|
||||||
|
public:
|
||||||
|
void addImage(const fs::path& path);
|
||||||
|
void pack(const fs::path& outputPath);
|
||||||
|
};
|
||||||
|
} // namespace sdl_gpu_test
|
||||||
|
|
||||||
|
#endif // !defined(SDL_GPU_TEST_PRIVATE_TEXTURE_PACKER_PACKER_HPP_INCLUDED)
|
Loading…
x
Reference in New Issue
Block a user