Initial Vectorail Core library

This commit is contained in:
2026-08-02 17:05:27 +02:00
commit d869edfaa8
15 changed files with 749 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
#include "vectorail/core/PngTexture.hpp"
#include "vectorail/core/gl_loader.hpp"
#include <png.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <span>
#include <vector>
namespace {
uint16_t u16be(std::span<const uint8_t> bytes, size_t offset) {
return static_cast<uint16_t>((static_cast<uint16_t>(bytes[offset]) << 8) | bytes[offset + 1]);
}
uint32_t u32be(std::span<const uint8_t> bytes, size_t offset) {
return (static_cast<uint32_t>(bytes[offset]) << 24) |
(static_cast<uint32_t>(bytes[offset + 1]) << 16) |
(static_cast<uint32_t>(bytes[offset + 2]) << 8) |
static_cast<uint32_t>(bytes[offset + 3]);
}
bool readFile(const std::string& path, std::vector<uint8_t>& bytes) {
std::ifstream file(path, std::ios::binary);
if (!file) return false;
file.seekg(0, std::ios::end);
const std::streamoff size = file.tellg();
if (size < 0) return false;
file.seekg(0, std::ios::beg);
bytes.assign(static_cast<size_t>(size), 0);
if (!bytes.empty()) file.read(reinterpret_cast<char*>(bytes.data()), size);
return static_cast<bool>(file) || file.eof();
}
struct PngMemoryReader {
std::span<const uint8_t> bytes;
size_t offset = 0;
};
void readPngMemory(png_structp png, png_bytep destination, png_size_t length) {
auto* reader = static_cast<PngMemoryReader*>(png_get_io_ptr(png));
if (!reader || length > reader->bytes.size() - reader->offset) {
png_error(png, "truncated PNG stream");
return;
}
std::memcpy(destination, reader->bytes.data() + reader->offset, length);
reader->offset += length;
}
bool decodePng(std::span<const uint8_t> bytes, DdsTexture& out, std::string* error) {
out = {};
if (bytes.size() < 8 || png_sig_cmp(bytes.data(), 0, 8) != 0) {
if (error) *error = "not a PNG stream";
return false;
}
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info = png ? png_create_info_struct(png) : nullptr;
if (!png || !info || setjmp(png_jmpbuf(png))) {
if (error) *error = "could not decode PNG";
if (png) png_destroy_read_struct(&png, info ? &info : nullptr, nullptr);
return false;
}
PngMemoryReader reader{bytes};
png_set_read_fn(png, &reader, readPngMemory);
png_read_info(png, info);
const png_uint_32 width = png_get_image_width(png, info);
const png_uint_32 height = png_get_image_height(png, info);
const int colorType = png_get_color_type(png, info);
const int bitDepth = png_get_bit_depth(png, info);
if (bitDepth == 16) png_set_strip_16(png);
if (colorType == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) png_set_expand_gray_1_2_4_to_8(png);
if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png);
if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png);
if ((colorType & PNG_COLOR_MASK_ALPHA) == 0) png_set_add_alpha(png, 0xff, PNG_FILLER_AFTER);
png_read_update_info(png, info);
std::vector<uint8_t> rgba(static_cast<size_t>(width) * height * 4);
std::vector<png_bytep> rows(height);
for (png_uint_32 y = 0; y < height; ++y) rows[y] = rgba.data() + static_cast<size_t>(y) * width * 4;
png_read_image(png, rows.data());
png_destroy_read_struct(&png, &info, nullptr);
glGenTextures(1, &out.id);
glBindTexture(GL_TEXTURE_2D, out.id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast<GLsizei>(width), static_cast<GLsizei>(height),
0, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data());
glBindTexture(GL_TEXTURE_2D, 0);
out.width = static_cast<int>(width);
out.height = static_cast<int>(height);
return true;
}
} // namespace
bool loadPngTexture(const std::string& path, DdsTexture& out, std::string* error) {
std::vector<uint8_t> bytes;
if (!readFile(path, bytes)) {
if (error) *error = "could not open PNG";
return false;
}
return decodePng(bytes, out, error);
}
bool loadPngTextureList(const std::string& path, std::vector<DdsTexture>& out, std::string* error) {
out.clear();
std::vector<uint8_t> bytes;
if (!readFile(path, bytes) || bytes.size() < 10) {
if (error) *error = "could not read texture list";
return false;
}
const std::span<const uint8_t> view(bytes);
const uint16_t count = u16be(view, 4);
if (6u + (static_cast<size_t>(count) + 1u) * 4u > bytes.size()) {
if (error) *error = "invalid texture-list offset table";
return false;
}
out.assign(count, {});
int loaded = 0;
for (uint16_t index = 0; index < count; ++index) {
const size_t begin = u32be(view, 6 + static_cast<size_t>(index) * 4);
const size_t end = u32be(view, 10 + static_cast<size_t>(index) * 4);
if (begin == end) continue;
if (begin > end || end > bytes.size()) {
if (error) *error = "invalid embedded PNG range";
return false;
}
std::string pngError;
if (!decodePng(view.subspan(begin, end - begin), out[index], &pngError)) {
if (error) *error = "texture " + std::to_string(index) + ": " + pngError;
return false;
}
++loaded;
}
if (loaded == 0) {
if (error) *error = "texture list has no PNG entries";
return false;
}
return true;
}