feat(host): vendor PyroWave + minimal Granite subset as crates/pyrowave-sys

Phase 0 of design/pyrowave-codec-plan.md — the opt-in wired-LAN ultra-low-
latency codec. Vendored at upstream 509e4f88 (API 0.4.0, Granite 44362775,
volk + vulkan-headers pins in PUNKTFUNK-VENDOR.txt), pruned to the 6.6 MB
the standalone no-renderer build needs; scripts/vendor-pyrowave.sh
reproduces the tree (a pin bump is protocol-affecting, plan §4.2).

build.rs drives the wrapper CMakeLists (static archives incl. a static
C-API lib upstream only ships shared) + bindgen over pyrowave.h; Linux and
Windows only, empty stub elsewhere (Apple gets a native Metal port, §4.7).
Offline-safe by construction: no network, no system lib, vendored Vulkan
headers — same model as the opus dep (flatpak builder has no network).

Phase-0 validation on .21 (RTX 5070 Ti, driver 610.43.03):
- upstream pyrowave-c-test + interop test (incl. dmabuf/DRM-modifier
  Vulkan<->Vulkan) pass, from the pristine AND the pruned tree
- GPU kernel times at ~1.6 bpp noise: encode/decode 0.090/0.042 ms @800p,
  0.146/0.067 @1080p, 0.226/0.103 @1440p, 0.477/0.201 @4K — order of
  magnitude under NVENC's 1-2 ms retrieve, CBR lands within ~100 B of
  target
- cargo test -p pyrowave-sys green (static link + API-version pin check)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 00:35:10 +02:00
parent 1b73361372
commit 4c3b11445c
396 changed files with 140058 additions and 0 deletions
@@ -0,0 +1,346 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "memory_mapped_texture.hpp"
#include <string.h>
#include <stdlib.h>
namespace Vulkan
{
struct MemoryMappedHeader
{
char magic[16];
VkImageType type;
VkFormat format;
uint32_t width;
uint32_t height;
uint32_t depth;
uint32_t layers;
uint32_t levels;
uint32_t flags;
uint64_t payload_size;
uint64_t reserved1;
};
static const size_t header_size = 16 + 8 * 4 + 2 * 8;
static_assert(sizeof(MemoryMappedHeader) == header_size, "Header size is not properly packed.");
static const char MAGIC[16] = "GRANITE TEXFMT1";
void MemoryMappedTexture::set_generate_mipmaps_on_load(bool enable)
{
mipgen_on_load = enable;
}
void MemoryMappedTexture::set_flags(MemoryMappedTextureFlags flags)
{
bool new_cube = (flags & MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT) != 0;
if (new_cube != cube)
abort();
set_generate_mipmaps_on_load((flags & MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT) != 0);
}
MemoryMappedTextureFlags MemoryMappedTexture::get_flags() const
{
MemoryMappedTextureFlags flags = 0;
if (cube)
flags |= MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT;
if (mipgen_on_load)
flags |= MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT;
flags |= swizzle.r << MEMORY_MAPPED_TEXTURE_SWIZZLE_R_SHIFT;
flags |= swizzle.g << MEMORY_MAPPED_TEXTURE_SWIZZLE_G_SHIFT;
flags |= swizzle.b << MEMORY_MAPPED_TEXTURE_SWIZZLE_B_SHIFT;
flags |= swizzle.a << MEMORY_MAPPED_TEXTURE_SWIZZLE_A_SHIFT;
return flags;
}
void MemoryMappedTexture::set_1d(VkFormat format, uint32_t width, uint32_t layers, uint32_t levels)
{
layout.set_1d(format, width, layers, levels);
cube = false;
}
void MemoryMappedTexture::set_2d(VkFormat format, uint32_t width, uint32_t height, uint32_t layers, uint32_t levels)
{
layout.set_2d(format, width, height, layers, levels);
cube = false;
}
void MemoryMappedTexture::set_3d(VkFormat format, uint32_t width, uint32_t height, uint32_t depth, uint32_t levels)
{
layout.set_3d(format, width, height, depth, levels);
cube = false;
}
void MemoryMappedTexture::set_cube(VkFormat format, uint32_t size, uint32_t cube_layers, uint32_t levels)
{
layout.set_2d(format, size, size, cube_layers * 6, levels);
cube = true;
}
bool MemoryMappedTexture::copy_to_path(Granite::Filesystem &fs, const std::string &path)
{
if (layout.get_required_size() == 0 || !mapped)
return false;
auto target_file = fs.open(path, Granite::FileMode::WriteOnly);
if (!target_file)
return false;
auto new_mapped = target_file->map_write(get_required_size());
if (!new_mapped)
return false;
memcpy(new_mapped->mutable_data(), mapped, get_required_size());
return true;
}
bool MemoryMappedTexture::map_write(Granite::FileMappingHandle new_file)
{
file = std::move(new_file);
mapped = file->mutable_data<uint8_t>();
MemoryMappedHeader header = {};
memcpy(header.magic, MAGIC, sizeof(MAGIC));
header.width = layout.get_width();
header.height = layout.get_height();
header.depth = layout.get_depth();
header.flags = get_flags();
header.layers = layout.get_layers();
header.levels = layout.get_levels();
header.payload_size = layout.get_required_size();
header.type = layout.get_image_type();
header.format = layout.get_format();
memcpy(mapped, &header, sizeof(header));
layout.set_buffer(mapped + sizeof(header), layout.get_required_size());
return true;
}
bool MemoryMappedTexture::map_write(Granite::Filesystem &fs, const std::string &path)
{
if (layout.get_required_size() == 0)
return false;
auto new_file = fs.open(path, Granite::FileMode::WriteOnly);
if (!new_file)
return false;
auto map_handle = new_file->map_write(get_required_size());
if (!map_handle)
return false;
return map_write(std::move(map_handle));
}
struct ScratchFile final : Granite::File
{
ScratchFile(const void *mapped, size_t size)
{
data.resize(size);
if (mapped)
memcpy(data.data(), mapped, size);
}
Granite::FileMappingHandle map_subset(uint64_t offset, size_t range) override
{
if (offset + range > data.size())
return {};
return Util::make_handle<Granite::FileMapping>(
reference_from_this(), offset,
data.data() + offset, range,
0, range);
}
Granite::FileMappingHandle map_write(size_t size) override
{
data.resize(size);
return map_subset(0, size);
}
void unmap(void *, size_t) override
{
}
uint64_t get_size() override
{
return data.size();
}
std::vector<uint8_t> data;
};
void MemoryMappedTexture::make_local_copy()
{
if (empty())
return;
auto new_file = Util::make_handle<ScratchFile>(mapped, get_required_size());
file = new_file->map();
mapped = file->mutable_data<uint8_t>();
layout.set_buffer(mapped + sizeof(MemoryMappedHeader),
get_required_size() - sizeof(MemoryMappedHeader));
}
bool MemoryMappedTexture::map_write_scratch()
{
if (layout.get_required_size() == 0)
return false;
auto new_file = Util::make_handle<ScratchFile>(nullptr, get_required_size());
if (new_file->get_size() < sizeof(MemoryMappedHeader))
return false;
auto new_mapped = new_file->map_write(get_required_size());
return map_write(std::move(new_mapped));
}
size_t MemoryMappedTexture::get_required_size() const
{
return layout.get_required_size() + sizeof(MemoryMappedHeader);
}
void MemoryMappedTexture::set_swizzle(const VkComponentMapping &swizzle_)
{
swizzle = swizzle_;
}
static void remap(VkComponentSwizzle &output, VkComponentSwizzle input,
const VkComponentMapping &mapping, VkComponentSwizzle identity)
{
if (input == VK_COMPONENT_SWIZZLE_IDENTITY)
input = identity;
switch (input)
{
case VK_COMPONENT_SWIZZLE_R:
output = mapping.r;
break;
case VK_COMPONENT_SWIZZLE_G:
output = mapping.g;
break;
case VK_COMPONENT_SWIZZLE_B:
output = mapping.b;
break;
case VK_COMPONENT_SWIZZLE_A:
output = mapping.a;
break;
case VK_COMPONENT_SWIZZLE_ONE:
case VK_COMPONENT_SWIZZLE_ZERO:
output = input;
break;
default:
output = VK_COMPONENT_SWIZZLE_IDENTITY;
break;
}
}
void MemoryMappedTexture::remap_swizzle(VkComponentMapping &mapping) const
{
VkComponentMapping new_mapping;
remap(new_mapping.r, swizzle.r, mapping, VK_COMPONENT_SWIZZLE_R);
remap(new_mapping.g, swizzle.g, mapping, VK_COMPONENT_SWIZZLE_G);
remap(new_mapping.b, swizzle.b, mapping, VK_COMPONENT_SWIZZLE_B);
remap(new_mapping.a, swizzle.a, mapping, VK_COMPONENT_SWIZZLE_A);
mapping = new_mapping;
}
bool MemoryMappedTexture::map_copy(const void *mapped_, size_t size)
{
auto new_file = Util::make_handle<ScratchFile>(mapped_, size);
if (new_file->get_size() < sizeof(MemoryMappedHeader))
return false;
auto new_mapped = new_file->map();
return map_read(std::move(new_mapped));
}
bool MemoryMappedTexture::map_read(Granite::FileMappingHandle new_file)
{
file = std::move(new_file);
mapped = const_cast<uint8_t *>(file->data<uint8_t>());
auto *header = reinterpret_cast<const MemoryMappedHeader *>(mapped);
switch (header->type)
{
case VK_IMAGE_TYPE_1D:
layout.set_1d(header->format, header->width, header->layers, header->levels);
break;
case VK_IMAGE_TYPE_2D:
layout.set_2d(header->format, header->width, header->height, header->layers, header->levels);
break;
case VK_IMAGE_TYPE_3D:
layout.set_3d(header->format, header->width, header->height, header->depth, header->levels);
break;
default:
return false;
}
cube = (header->flags & MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT) != 0;
mipgen_on_load = (header->flags & MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT) != 0;
swizzle.r = static_cast<VkComponentSwizzle>((header->flags >> MEMORY_MAPPED_TEXTURE_SWIZZLE_R_SHIFT) & MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK);
swizzle.g = static_cast<VkComponentSwizzle>((header->flags >> MEMORY_MAPPED_TEXTURE_SWIZZLE_G_SHIFT) & MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK);
swizzle.b = static_cast<VkComponentSwizzle>((header->flags >> MEMORY_MAPPED_TEXTURE_SWIZZLE_B_SHIFT) & MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK);
swizzle.a = static_cast<VkComponentSwizzle>((header->flags >> MEMORY_MAPPED_TEXTURE_SWIZZLE_A_SHIFT) & MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK);
if ((layout.get_required_size() + sizeof(MemoryMappedHeader)) < file->get_size())
return false;
if (header->payload_size != layout.get_required_size())
return false;
layout.set_buffer(static_cast<uint8_t *>(mapped) + sizeof(MemoryMappedHeader), header->payload_size);
return true;
}
bool MemoryMappedTexture::map_read(Granite::Filesystem &fs, const std::string &path)
{
auto loaded_file = fs.open(path, Granite::FileMode::ReadOnly);
if (!loaded_file)
return false;
if (loaded_file->get_size() < sizeof(MemoryMappedHeader))
return false;
auto new_mapped = loaded_file->map();
if (!new_mapped)
return false;
return map_read(std::move(new_mapped));
}
bool MemoryMappedTexture::is_header(const void *mapped_, size_t size)
{
if (size < sizeof(MemoryMappedHeader))
return false;
return memcmp(mapped_, MAGIC, sizeof(MAGIC)) == 0;
}
}
@@ -0,0 +1,94 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "texture_format.hpp"
#include "filesystem.hpp"
namespace Vulkan
{
enum MemoryMappedTextureFlagBits
{
MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT = 1 << 0,
MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT = 1 << 1,
MEMORY_MAPPED_TEXTURE_SWIZZLE_R_SHIFT = 16,
MEMORY_MAPPED_TEXTURE_SWIZZLE_G_SHIFT = 19,
MEMORY_MAPPED_TEXTURE_SWIZZLE_B_SHIFT = 22,
MEMORY_MAPPED_TEXTURE_SWIZZLE_A_SHIFT = 25,
MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK = 0x7
};
using MemoryMappedTextureFlags = uint32_t;
class MemoryMappedTexture
{
public:
void set_1d(VkFormat format, uint32_t width, uint32_t layers = 1, uint32_t levels = 1);
void set_2d(VkFormat format, uint32_t width, uint32_t height, uint32_t layers = 1, uint32_t levels = 1);
void set_3d(VkFormat format, uint32_t width, uint32_t height, uint32_t depth, uint32_t levels = 1);
void set_cube(VkFormat format, uint32_t size, uint32_t cube_layers = 1, uint32_t levels = 1);
static bool is_header(const void *mapped, size_t size);
bool map_write(Granite::Filesystem &fs, const std::string &path);
bool map_write(Granite::FileMappingHandle file);
bool map_read(Granite::Filesystem &fs, const std::string &path);
bool map_read(Granite::FileMappingHandle file);
bool map_copy(const void *mapped, size_t size);
bool map_write_scratch();
bool copy_to_path(Granite::Filesystem &fs, const std::string &path);
void make_local_copy();
inline const Vulkan::TextureFormatLayout &get_layout() const
{
return layout;
}
void set_generate_mipmaps_on_load(bool enable = true);
MemoryMappedTextureFlags get_flags() const;
void set_flags(MemoryMappedTextureFlags flags);
size_t get_required_size() const;
void set_swizzle(const VkComponentMapping &swizzle);
void remap_swizzle(VkComponentMapping &mapping) const;
inline bool empty() const
{
return get_layout().get_required_size() == 0;
}
private:
Vulkan::TextureFormatLayout layout;
Granite::FileMappingHandle file;
uint8_t *mapped = nullptr;
bool cube = false;
bool mipgen_on_load = false;
VkComponentMapping swizzle = {
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A,
};
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "texture_format.hpp"
#include "command_buffer.hpp"
#include "device.hpp"
namespace Granite
{
Vulkan::ImageHandle decode_compressed_image(Vulkan::CommandBuffer &cmd, const Vulkan::TextureFormatLayout &layout,
VkFormat preferred_decode_format,
const VkComponentMapping &swizzle = {
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A,
});
}
@@ -0,0 +1,124 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "texture_files.hpp"
#include "stb_image.h"
#include "filesystem.hpp"
#include "muglm/muglm_impl.hpp"
#include <string.h>
namespace Vulkan
{
static MemoryMappedTexture load_stb(const void *data, size_t size, ColorSpace color)
{
int width, height;
int components;
auto *buffer = stbi_load_from_memory(static_cast<const stbi_uc *>(data), size, &width, &height, &components, 4);
if (!buffer)
return {};
MemoryMappedTexture tex;
tex.set_2d(color == ColorSpace::sRGB ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM, width, height);
tex.set_generate_mipmaps_on_load(true);
if (!tex.map_write_scratch())
return {};
memcpy(tex.get_layout().data(), buffer, width * height * 4);
stbi_image_free(buffer);
return tex;
}
static MemoryMappedTexture load_hdr(const void *data, size_t size)
{
int width, height;
int components;
auto *buffer = stbi_loadf_from_memory(static_cast<const stbi_uc *>(data), size, &width, &height, &components, 3);
MemoryMappedTexture tex;
tex.set_2d(VK_FORMAT_R16G16B16A16_SFLOAT, width, height);
if (!tex.map_write_scratch())
return {};
tex.set_generate_mipmaps_on_load(true);
auto *converted = static_cast<muglm::u16vec4 *>(tex.get_layout().data());
for (int i = 0; i < width * height; i++)
{
converted[i] = muglm::floatToHalf(muglm::vec4(buffer[3 * i + 0], buffer[3 * i + 1], buffer[3 * i + 2], 1.0f));
}
stbi_image_free(buffer);
return tex;
}
MemoryMappedTexture load_texture_from_memory(const void *data, size_t size, ColorSpace color)
{
static const uint8_t png_magic[] = {
0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a,
};
static const uint8_t jpg_magic[] = {
0xff, 0xd8,
};
static const uint8_t hdr_magic[] = {
0x23, 0x3f, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x0a,
};
if (size >= sizeof(png_magic) && memcmp(data, png_magic, sizeof(png_magic)) == 0)
return load_stb(data, size, color);
else if (size >= 2 && memcmp(data, jpg_magic, sizeof(jpg_magic)) == 0)
return load_stb(data, size, color);
else if (size >= sizeof(hdr_magic) && memcmp(data, hdr_magic, sizeof(hdr_magic)) == 0)
return load_hdr(data, size);
else if (MemoryMappedTexture::is_header(data, size))
{
MemoryMappedTexture mapped;
mapped.map_copy(data, size);
return mapped;
}
else
{
// YOLO!
return load_stb(data, size, color);
}
}
MemoryMappedTexture load_texture_from_file(Granite::Filesystem &fs, const std::string &path, ColorSpace color)
{
auto file = fs.open(path, Granite::FileMode::ReadOnly);
if (!file)
return {};
auto mapped = file->map();
if (!mapped)
return {};
if (MemoryMappedTexture::is_header(mapped->data(), mapped->get_size()))
{
MemoryMappedTexture tex;
tex.map_read(std::move(mapped));
return tex;
}
return load_texture_from_memory(mapped->data(), mapped->get_size(), color);
}
}
@@ -0,0 +1,44 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "format.hpp"
#include "memory_mapped_texture.hpp"
namespace Granite
{
class Filesystem;
}
namespace Vulkan
{
enum class ColorSpace
{
Linear,
sRGB
};
MemoryMappedTexture load_texture_from_file(Granite::Filesystem &fs, const std::string &path, ColorSpace color = ColorSpace::sRGB);
MemoryMappedTexture load_texture_from_memory(const void *data, size_t size,
ColorSpace color = ColorSpace::sRGB);
}
@@ -0,0 +1,518 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define NOMINMAX
#include "texture_format.hpp"
#include "format.hpp"
#include <algorithm>
namespace Vulkan
{
uint32_t TextureFormatLayout::num_miplevels(uint32_t width, uint32_t height, uint32_t depth)
{
uint32_t size = unsigned(std::max(std::max(width, height), depth));
uint32_t levels = 0;
while (size)
{
levels++;
size >>= 1;
}
return levels;
}
void TextureFormatLayout::format_block_dim(VkFormat format, uint32_t &width, uint32_t &height)
{
#define fmt(x, w, h) \
case VK_FORMAT_##x: \
width = w; \
height = h; \
break
switch (format)
{
fmt(ETC2_R8G8B8A8_UNORM_BLOCK, 4, 4);
fmt(ETC2_R8G8B8A8_SRGB_BLOCK, 4, 4);
fmt(ETC2_R8G8B8A1_UNORM_BLOCK, 4, 4);
fmt(ETC2_R8G8B8A1_SRGB_BLOCK, 4, 4);
fmt(ETC2_R8G8B8_UNORM_BLOCK, 4, 4);
fmt(ETC2_R8G8B8_SRGB_BLOCK, 4, 4);
fmt(EAC_R11_UNORM_BLOCK, 4, 4);
fmt(EAC_R11_SNORM_BLOCK, 4, 4);
fmt(EAC_R11G11_UNORM_BLOCK, 4, 4);
fmt(EAC_R11G11_SNORM_BLOCK, 4, 4);
fmt(BC1_RGB_UNORM_BLOCK, 4, 4);
fmt(BC1_RGB_SRGB_BLOCK, 4, 4);
fmt(BC1_RGBA_UNORM_BLOCK, 4, 4);
fmt(BC1_RGBA_SRGB_BLOCK, 4, 4);
fmt(BC2_UNORM_BLOCK, 4, 4);
fmt(BC2_SRGB_BLOCK, 4, 4);
fmt(BC3_UNORM_BLOCK, 4, 4);
fmt(BC3_SRGB_BLOCK, 4, 4);
fmt(BC4_UNORM_BLOCK, 4, 4);
fmt(BC4_SNORM_BLOCK, 4, 4);
fmt(BC5_UNORM_BLOCK, 4, 4);
fmt(BC5_SNORM_BLOCK, 4, 4);
fmt(BC6H_UFLOAT_BLOCK, 4, 4);
fmt(BC6H_SFLOAT_BLOCK, 4, 4);
fmt(BC7_SRGB_BLOCK, 4, 4);
fmt(BC7_UNORM_BLOCK, 4, 4);
#define astc_fmt(w, h) \
fmt(ASTC_##w##x##h##_UNORM_BLOCK, w, h); \
fmt(ASTC_##w##x##h##_SRGB_BLOCK, w, h); \
fmt(ASTC_##w##x##h##_SFLOAT_BLOCK_EXT, w, h)
astc_fmt(4, 4);
astc_fmt(5, 4);
astc_fmt(5, 5);
astc_fmt(6, 5);
astc_fmt(6, 6);
astc_fmt(8, 5);
astc_fmt(8, 6);
astc_fmt(8, 8);
astc_fmt(10, 5);
astc_fmt(10, 6);
astc_fmt(10, 8);
astc_fmt(10, 10);
astc_fmt(12, 10);
astc_fmt(12, 12);
default:
width = 1;
height = 1;
break;
}
#undef fmt
#undef astc_fmt
}
uint32_t TextureFormatLayout::format_block_size(VkFormat format, VkImageAspectFlags aspect)
{
#define fmt(x, bpp) \
case VK_FORMAT_##x: \
return bpp
#define fmt2(x, bpp0, bpp1) \
case VK_FORMAT_##x: \
return aspect == VK_IMAGE_ASPECT_PLANE_0_BIT ? bpp0 : bpp1
switch (format)
{
fmt(R4G4_UNORM_PACK8, 1);
fmt(R4G4B4A4_UNORM_PACK16, 2);
fmt(B4G4R4A4_UNORM_PACK16, 2);
fmt(R5G6B5_UNORM_PACK16, 2);
fmt(B5G6R5_UNORM_PACK16, 2);
fmt(R5G5B5A1_UNORM_PACK16, 2);
fmt(B5G5R5A1_UNORM_PACK16, 2);
fmt(A1R5G5B5_UNORM_PACK16, 2);
fmt(R8_UNORM, 1);
fmt(R8_SNORM, 1);
fmt(R8_USCALED, 1);
fmt(R8_SSCALED, 1);
fmt(R8_UINT, 1);
fmt(R8_SINT, 1);
fmt(R8_SRGB, 1);
fmt(R8G8_UNORM, 2);
fmt(R8G8_SNORM, 2);
fmt(R8G8_USCALED, 2);
fmt(R8G8_SSCALED, 2);
fmt(R8G8_UINT, 2);
fmt(R8G8_SINT, 2);
fmt(R8G8_SRGB, 2);
fmt(R8G8B8_UNORM, 3);
fmt(R8G8B8_SNORM, 3);
fmt(R8G8B8_USCALED, 3);
fmt(R8G8B8_SSCALED, 3);
fmt(R8G8B8_UINT, 3);
fmt(R8G8B8_SINT, 3);
fmt(R8G8B8_SRGB, 3);
fmt(R8G8B8A8_UNORM, 4);
fmt(R8G8B8A8_SNORM, 4);
fmt(R8G8B8A8_USCALED, 4);
fmt(R8G8B8A8_SSCALED, 4);
fmt(R8G8B8A8_UINT, 4);
fmt(R8G8B8A8_SINT, 4);
fmt(R8G8B8A8_SRGB, 4);
fmt(B8G8R8A8_UNORM, 4);
fmt(B8G8R8A8_SNORM, 4);
fmt(B8G8R8A8_USCALED, 4);
fmt(B8G8R8A8_SSCALED, 4);
fmt(B8G8R8A8_UINT, 4);
fmt(B8G8R8A8_SINT, 4);
fmt(B8G8R8A8_SRGB, 4);
fmt(A8B8G8R8_UNORM_PACK32, 4);
fmt(A8B8G8R8_SNORM_PACK32, 4);
fmt(A8B8G8R8_USCALED_PACK32, 4);
fmt(A8B8G8R8_SSCALED_PACK32, 4);
fmt(A8B8G8R8_UINT_PACK32, 4);
fmt(A8B8G8R8_SINT_PACK32, 4);
fmt(A8B8G8R8_SRGB_PACK32, 4);
fmt(A2B10G10R10_UNORM_PACK32, 4);
fmt(A2B10G10R10_SNORM_PACK32, 4);
fmt(A2B10G10R10_USCALED_PACK32, 4);
fmt(A2B10G10R10_SSCALED_PACK32, 4);
fmt(A2B10G10R10_UINT_PACK32, 4);
fmt(A2B10G10R10_SINT_PACK32, 4);
fmt(A2R10G10B10_UNORM_PACK32, 4);
fmt(A2R10G10B10_SNORM_PACK32, 4);
fmt(A2R10G10B10_USCALED_PACK32, 4);
fmt(A2R10G10B10_SSCALED_PACK32, 4);
fmt(A2R10G10B10_UINT_PACK32, 4);
fmt(A2R10G10B10_SINT_PACK32, 4);
fmt(R16_UNORM, 2);
fmt(R16_SNORM, 2);
fmt(R16_USCALED, 2);
fmt(R16_SSCALED, 2);
fmt(R16_UINT, 2);
fmt(R16_SINT, 2);
fmt(R16_SFLOAT, 2);
fmt(R16G16_UNORM, 4);
fmt(R16G16_SNORM, 4);
fmt(R16G16_USCALED, 4);
fmt(R16G16_SSCALED, 4);
fmt(R16G16_UINT, 4);
fmt(R16G16_SINT, 4);
fmt(R16G16_SFLOAT, 4);
fmt(R16G16B16_UNORM, 6);
fmt(R16G16B16_SNORM, 6);
fmt(R16G16B16_USCALED, 6);
fmt(R16G16B16_SSCALED, 6);
fmt(R16G16B16_UINT, 6);
fmt(R16G16B16_SINT, 6);
fmt(R16G16B16_SFLOAT, 6);
fmt(R16G16B16A16_UNORM, 8);
fmt(R16G16B16A16_SNORM, 8);
fmt(R16G16B16A16_USCALED, 8);
fmt(R16G16B16A16_SSCALED, 8);
fmt(R16G16B16A16_UINT, 8);
fmt(R16G16B16A16_SINT, 8);
fmt(R16G16B16A16_SFLOAT, 8);
fmt(R32_UINT, 4);
fmt(R32_SINT, 4);
fmt(R32_SFLOAT, 4);
fmt(R32G32_UINT, 8);
fmt(R32G32_SINT, 8);
fmt(R32G32_SFLOAT, 8);
fmt(R32G32B32_UINT, 12);
fmt(R32G32B32_SINT, 12);
fmt(R32G32B32_SFLOAT, 12);
fmt(R32G32B32A32_UINT, 16);
fmt(R32G32B32A32_SINT, 16);
fmt(R32G32B32A32_SFLOAT, 16);
fmt(R64_UINT, 8);
fmt(R64_SINT, 8);
fmt(R64_SFLOAT, 8);
fmt(R64G64_UINT, 16);
fmt(R64G64_SINT, 16);
fmt(R64G64_SFLOAT, 16);
fmt(R64G64B64_UINT, 24);
fmt(R64G64B64_SINT, 24);
fmt(R64G64B64_SFLOAT, 24);
fmt(R64G64B64A64_UINT, 32);
fmt(R64G64B64A64_SINT, 32);
fmt(R64G64B64A64_SFLOAT, 32);
fmt(B10G11R11_UFLOAT_PACK32, 4);
fmt(E5B9G9R9_UFLOAT_PACK32, 4);
fmt(D16_UNORM, 2);
fmt(X8_D24_UNORM_PACK32, 4);
fmt(D32_SFLOAT, 4);
fmt(S8_UINT, 1);
case VK_FORMAT_D16_UNORM_S8_UINT:
return aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? 2 : 1;
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? 4 : 1;
// ETC2
fmt(ETC2_R8G8B8A8_UNORM_BLOCK, 16);
fmt(ETC2_R8G8B8A8_SRGB_BLOCK, 16);
fmt(ETC2_R8G8B8A1_UNORM_BLOCK, 8);
fmt(ETC2_R8G8B8A1_SRGB_BLOCK, 8);
fmt(ETC2_R8G8B8_UNORM_BLOCK, 8);
fmt(ETC2_R8G8B8_SRGB_BLOCK, 8);
fmt(EAC_R11_UNORM_BLOCK, 8);
fmt(EAC_R11_SNORM_BLOCK, 8);
fmt(EAC_R11G11_UNORM_BLOCK, 16);
fmt(EAC_R11G11_SNORM_BLOCK, 16);
// BC
fmt(BC1_RGB_UNORM_BLOCK, 8);
fmt(BC1_RGB_SRGB_BLOCK, 8);
fmt(BC1_RGBA_UNORM_BLOCK, 8);
fmt(BC1_RGBA_SRGB_BLOCK, 8);
fmt(BC2_UNORM_BLOCK, 16);
fmt(BC2_SRGB_BLOCK, 16);
fmt(BC3_UNORM_BLOCK, 16);
fmt(BC3_SRGB_BLOCK, 16);
fmt(BC4_UNORM_BLOCK, 8);
fmt(BC4_SNORM_BLOCK, 8);
fmt(BC5_UNORM_BLOCK, 16);
fmt(BC5_SNORM_BLOCK, 16);
fmt(BC6H_UFLOAT_BLOCK, 16);
fmt(BC6H_SFLOAT_BLOCK, 16);
fmt(BC7_SRGB_BLOCK, 16);
fmt(BC7_UNORM_BLOCK, 16);
// ASTC
#define astc_fmt(w, h) \
fmt(ASTC_##w##x##h##_UNORM_BLOCK, 16); \
fmt(ASTC_##w##x##h##_SRGB_BLOCK, 16); \
fmt(ASTC_##w##x##h##_SFLOAT_BLOCK_EXT, 16)
astc_fmt(4, 4);
astc_fmt(5, 4);
astc_fmt(5, 5);
astc_fmt(6, 5);
astc_fmt(6, 6);
astc_fmt(8, 5);
astc_fmt(8, 6);
astc_fmt(8, 8);
astc_fmt(10, 5);
astc_fmt(10, 6);
astc_fmt(10, 8);
astc_fmt(10, 10);
astc_fmt(12, 10);
astc_fmt(12, 12);
fmt(G8B8G8R8_422_UNORM, 4);
fmt(B8G8R8G8_422_UNORM, 4);
fmt(G8_B8_R8_3PLANE_420_UNORM, 1);
fmt2(G8_B8R8_2PLANE_420_UNORM, 1, 2);
fmt(G8_B8_R8_3PLANE_422_UNORM, 1);
fmt2(G8_B8R8_2PLANE_422_UNORM, 1, 2);
fmt(G8_B8_R8_3PLANE_444_UNORM, 1);
fmt(R10X6_UNORM_PACK16, 2);
fmt(R10X6G10X6_UNORM_2PACK16, 4);
fmt(R10X6G10X6B10X6A10X6_UNORM_4PACK16, 8);
fmt(G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, 8);
fmt(B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, 8);
fmt(G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, 2);
fmt(G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, 2);
fmt(G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, 2);
fmt2(G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, 2, 4);
fmt2(G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, 2, 4);
fmt(R12X4_UNORM_PACK16, 2);
fmt(R12X4G12X4_UNORM_2PACK16, 4);
fmt(R12X4G12X4B12X4A12X4_UNORM_4PACK16, 8);
fmt(G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, 8);
fmt(B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, 8);
fmt(G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, 2);
fmt(G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, 2);
fmt(G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, 2);
fmt2(G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, 2, 4);
fmt2(G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, 2, 4);
fmt(G16B16G16R16_422_UNORM, 8);
fmt(B16G16R16G16_422_UNORM, 8);
fmt(G16_B16_R16_3PLANE_420_UNORM, 2);
fmt(G16_B16_R16_3PLANE_422_UNORM, 2);
fmt(G16_B16_R16_3PLANE_444_UNORM, 2);
fmt2(G16_B16R16_2PLANE_420_UNORM, 2, 4);
fmt2(G16_B16R16_2PLANE_422_UNORM, 2, 4);
default:
assert(0 && "Unknown format.");
return 0;
}
#undef fmt
#undef fmt2
#undef astc_fmt
}
void TextureFormatLayout::fill_mipinfo(uint32_t width, uint32_t height, uint32_t depth)
{
block_stride = format_block_size(format, 0);
format_block_dim(format, block_dim_x, block_dim_y);
if (mip_levels == 0)
mip_levels = num_miplevels(width, height, depth);
size_t offset = 0;
for (uint32_t mip = 0; mip < mip_levels; mip++)
{
offset = (offset + 15) & ~15;
uint32_t blocks_x = (width + block_dim_x - 1) / block_dim_x;
uint32_t blocks_y = (height + block_dim_y - 1) / block_dim_y;
size_t mip_size = blocks_x * blocks_y * array_layers * depth * block_stride;
mips[mip].offset = offset;
mips[mip].block_row_length = blocks_x;
mips[mip].block_image_height = blocks_y;
mips[mip].row_length = blocks_x * block_dim_x;
mips[mip].image_height = blocks_y * block_dim_y;
mips[mip].width = width;
mips[mip].height = height;
mips[mip].depth = depth;
offset += mip_size;
width = std::max((width >> 1u), 1u);
height = std::max((height >> 1u), 1u);
depth = std::max((depth >> 1u), 1u);
}
required_size = offset;
}
void TextureFormatLayout::set_1d(VkFormat format_, uint32_t width, uint32_t array_layers_, uint32_t mip_levels_)
{
image_type = VK_IMAGE_TYPE_1D;
format = format_;
array_layers = array_layers_;
mip_levels = mip_levels_;
fill_mipinfo(width, 1, 1);
}
void TextureFormatLayout::set_2d(VkFormat format_, uint32_t width, uint32_t height,
uint32_t array_layers_, uint32_t mip_levels_)
{
image_type = VK_IMAGE_TYPE_2D;
format = format_;
array_layers = array_layers_;
mip_levels = mip_levels_;
fill_mipinfo(width, height, 1);
}
void TextureFormatLayout::set_3d(VkFormat format_, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels_)
{
image_type = VK_IMAGE_TYPE_3D;
format = format_;
array_layers = 1;
mip_levels = mip_levels_;
fill_mipinfo(width, height, depth);
}
void TextureFormatLayout::set_buffer(void *buffer_, size_t size)
{
buffer = static_cast<uint8_t *>(buffer_);
buffer_size = size;
}
uint32_t TextureFormatLayout::get_width(uint32_t mip) const
{
return mips[mip].width;
}
uint32_t TextureFormatLayout::get_height(uint32_t mip) const
{
return mips[mip].height;
}
uint32_t TextureFormatLayout::get_depth(uint32_t mip) const
{
return mips[mip].depth;
}
uint32_t TextureFormatLayout::get_layers() const
{
return array_layers;
}
VkImageType TextureFormatLayout::get_image_type() const
{
return image_type;
}
VkFormat TextureFormatLayout::get_format() const
{
return format;
}
uint32_t TextureFormatLayout::get_block_stride() const
{
return block_stride;
}
uint32_t TextureFormatLayout::get_levels() const
{
return mip_levels;
}
size_t TextureFormatLayout::get_required_size() const
{
return required_size;
}
const TextureFormatLayout::MipInfo &TextureFormatLayout::get_mip_info(uint32_t mip) const
{
return mips[mip];
}
uint32_t TextureFormatLayout::get_block_dim_x() const
{
return block_dim_x;
}
uint32_t TextureFormatLayout::get_block_dim_y() const
{
return block_dim_y;
}
size_t TextureFormatLayout::row_byte_stride(uint32_t row_length) const
{
return ((row_length + block_dim_x - 1) / block_dim_x) * block_stride;
}
size_t TextureFormatLayout::layer_byte_stride(uint32_t image_height, size_t row_byte_stride) const
{
return ((image_height + block_dim_y - 1) / block_dim_y) * row_byte_stride;
}
void TextureFormatLayout::build_buffer_image_copies(Util::SmallVector<VkBufferImageCopy, 32> &copies) const
{
copies.resize(mip_levels);
for (unsigned level = 0; level < mip_levels; level++)
{
const auto &mip_info = mips[level];
auto &blit = copies[level];
blit = {};
blit.bufferOffset = mip_info.offset;
blit.bufferRowLength = mip_info.row_length;
blit.bufferImageHeight = mip_info.image_height;
blit.imageSubresource.aspectMask = format_to_aspect_mask(format);
blit.imageSubresource.mipLevel = level;
blit.imageSubresource.baseArrayLayer = 0;
blit.imageSubresource.layerCount = array_layers;
blit.imageExtent.width = mip_info.width;
blit.imageExtent.height = mip_info.height;
blit.imageExtent.depth = mip_info.depth;
}
}
}
@@ -0,0 +1,178 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include "small_vector.hpp"
#include <vector>
#include <stddef.h>
#include <assert.h>
namespace Vulkan
{
class TextureFormatLayout
{
public:
void set_1d(VkFormat format, uint32_t width, uint32_t array_layers = 1, uint32_t mip_levels = 1);
void set_2d(VkFormat format, uint32_t width, uint32_t height, uint32_t array_layers = 1, uint32_t mip_levels = 1);
void set_3d(VkFormat format, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels = 1);
static uint32_t format_block_size(VkFormat format, VkImageAspectFlags aspect);
static void format_block_dim(VkFormat format, uint32_t &width, uint32_t &height);
static uint32_t num_miplevels(uint32_t width, uint32_t height = 1, uint32_t depth = 1);
void set_buffer(void *buffer, size_t size);
inline void *get_buffer()
{
return buffer;
}
uint32_t get_width(uint32_t mip = 0) const;
uint32_t get_height(uint32_t mip = 0) const;
uint32_t get_depth(uint32_t mip = 0) const;
uint32_t get_levels() const;
uint32_t get_layers() const;
uint32_t get_block_stride() const;
uint32_t get_block_dim_x() const;
uint32_t get_block_dim_y() const;
VkImageType get_image_type() const;
VkFormat get_format() const;
size_t get_required_size() const;
size_t row_byte_stride(uint32_t row_length) const;
size_t layer_byte_stride(uint32_t row_length, size_t row_byte_stride) const;
inline size_t get_row_size(uint32_t mip) const
{
return size_t(mips[mip].block_row_length) * block_stride;
}
inline size_t get_layer_size(uint32_t mip) const
{
return size_t(mips[mip].block_image_height) * get_row_size(mip);
}
struct MipInfo
{
size_t offset = 0;
uint32_t width = 1;
uint32_t height = 1;
uint32_t depth = 1;
uint32_t block_image_height = 0;
uint32_t block_row_length = 0;
uint32_t image_height = 0;
uint32_t row_length = 0;
};
const MipInfo &get_mip_info(uint32_t mip) const;
inline void *data(uint32_t layer = 0, uint32_t mip = 0) const
{
assert(buffer);
assert(buffer_size == required_size);
auto &mip_info = mips[mip];
uint8_t *slice = buffer + mip_info.offset;
slice += block_stride * layer * mip_info.block_row_length * mip_info.block_image_height;
return slice;
}
template <typename T>
inline T *data_generic(uint32_t x, uint32_t y, uint32_t slice_index, uint32_t mip = 0) const
{
auto &mip_info = mips[mip];
T *slice = reinterpret_cast<T *>(buffer + mip_info.offset);
slice += slice_index * mip_info.block_row_length * mip_info.block_image_height;
slice += y * mip_info.block_row_length;
slice += x;
return slice;
}
inline void *data_opaque(uint32_t x, uint32_t y, uint32_t slice_index, uint32_t mip = 0) const
{
auto &mip_info = mips[mip];
uint8_t *slice = buffer + mip_info.offset;
size_t off = slice_index * mip_info.block_row_length * mip_info.block_image_height;
off += y * mip_info.block_row_length;
off += x;
return slice + off * block_stride;
}
template <typename T>
inline T *data_generic() const
{
return data_generic<T>(0, 0, 0, 0);
}
template <typename T>
inline T *data_1d(uint32_t x, uint32_t layer = 0, uint32_t mip = 0) const
{
assert(sizeof(T) == block_stride);
assert(buffer);
assert(image_type == VK_IMAGE_TYPE_1D);
assert(buffer_size == required_size);
return data_generic<T>(x, 0, layer, mip);
}
template <typename T>
inline T *data_2d(uint32_t x, uint32_t y, uint32_t layer = 0, uint32_t mip = 0) const
{
assert(sizeof(T) == block_stride);
assert(buffer);
assert(image_type == VK_IMAGE_TYPE_2D);
assert(buffer_size == required_size);
return data_generic<T>(x, y, layer, mip);
}
template <typename T>
inline T *data_3d(uint32_t x, uint32_t y, uint32_t z, uint32_t mip = 0) const
{
assert(sizeof(T) == block_stride);
assert(buffer);
assert(image_type == VK_IMAGE_TYPE_3D);
assert(buffer_size == required_size);
return data_generic<T>(x, y, z, mip);
}
void build_buffer_image_copies(Util::SmallVector<VkBufferImageCopy, 32> &copies) const;
private:
uint8_t *buffer = nullptr;
size_t buffer_size = 0;
VkImageType image_type = VK_IMAGE_TYPE_MAX_ENUM;
VkFormat format = VK_FORMAT_UNDEFINED;
size_t required_size = 0;
uint32_t block_stride = 1;
uint32_t mip_levels = 1;
uint32_t array_layers = 1;
uint32_t block_dim_x = 1;
uint32_t block_dim_y = 1;
MipInfo mips[16];
void fill_mipinfo(uint32_t width, uint32_t height, uint32_t depth);
};
}