/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ module; #include "vulkan/vulkan.h" export module Crafter.Graphics:Decompress; import :Device; import std; export namespace Crafter::Decompress { // GDeflate 1.0 requires each VkDecompressMemoryRegionEXT to cover one // 64 KiB tile. CompressedBlob stores one stream per RegionMeta — this // helper walks the stream's tile-stream header and emits per-tile // regions ready for vkCmdDecompressMemoryEXT. // // streamBytes is the raw stream bytes (CompressedBlob::bytes.subspan( // r.srcOffset, r.compressedSize)). srcBase is its GPU device address. // dstBase is the GPU device address of the first decompressed byte. inline void ExpandStreamToTileRegions( std::span streamBytes, VkDeviceAddress srcBase, VkDeviceAddress dstBase, std::vector& out) { if (streamBytes.empty()) return; constexpr std::size_t kTileSize = 64 * 1024; constexpr std::size_t kHeaderSize = 8; // TileStream wire layout (see vendored TileStream.h): // u8 id; u8 magic; u16 numTiles; // { u2 tileSizeIdx; u18 lastTileSize; u12 reserved; } packed into u32 const std::uint8_t* p = reinterpret_cast(streamBytes.data()); std::uint8_t id = p[0]; std::uint8_t magic = p[1]; if (id != (magic ^ 0xff)) { throw std::runtime_error("GDeflate tile-stream: bad id/magic"); } std::uint16_t numTiles = static_cast(p[2]) | static_cast(p[3]) << 8; std::uint32_t packed = static_cast(p[4]) | static_cast(p[5]) << 8 | static_cast(p[6]) << 16 | static_cast(p[7]) << 24; std::uint32_t lastTileSize = (packed >> 2) & 0x3FFFFu; const std::uint32_t* tileOffsets = reinterpret_cast( streamBytes.data() + kHeaderSize); const std::size_t dataStart = kHeaderSize + std::size_t(numTiles) * sizeof(std::uint32_t); out.reserve(out.size() + numTiles); for (std::uint32_t k = 0; k < numTiles; ++k) { std::size_t tileOff = (k > 0) ? tileOffsets[k] : 0; std::size_t compressedSize = (k + 1 < numTiles) ? std::size_t(tileOffsets[k + 1]) - tileOff : std::size_t(tileOffsets[0]); std::size_t decompressedSize = (k + 1 < numTiles) ? kTileSize : (lastTileSize != 0 ? std::size_t(lastTileSize) : kTileSize); out.push_back(VkDecompressMemoryRegionEXT { .srcAddress = srcBase + dataStart + tileOff, .dstAddress = dstBase + std::size_t(k) * kTileSize, .compressedSize = compressedSize, .decompressedSize = decompressedSize, }); } } // Records vkCmdDecompressMemoryEXT into `cmd` for the supplied regions // (caller pre-resolves srcAddress/dstAddress) and follows it with a // synchronization2 memory barrier so the consumer at (dstStage, dstAccess) // observes the decompressed bytes. // // Method is fixed at GDeflate 1.0 — Crafter.Asset's compressed file format // emits GDeflate streams. Caller must guarantee Device::memoryDecompressionSupported. inline void DecompressOnGPU( VkCommandBuffer cmd, std::span regions, VkPipelineStageFlags2 dstStage, VkAccessFlags2 dstAccess) { if (regions.empty()) return; VkDecompressMemoryInfoEXT info { .sType = VK_STRUCTURE_TYPE_DECOMPRESS_MEMORY_INFO_EXT, .pNext = nullptr, .decompressionMethod = VK_MEMORY_DECOMPRESSION_METHOD_GDEFLATE_1_0_BIT_EXT, .regionCount = static_cast(regions.size()), .pRegions = regions.data(), }; Device::vkCmdDecompressMemoryEXT(cmd, &info); VkMemoryBarrier2 barrier { .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, .pNext = nullptr, .srcStageMask = VK_PIPELINE_STAGE_2_MEMORY_DECOMPRESSION_BIT_EXT, .srcAccessMask = VK_ACCESS_2_MEMORY_DECOMPRESSION_WRITE_BIT_EXT, .dstStageMask = dstStage, .dstAccessMask = dstAccess, }; VkDependencyInfo dep { .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .pNext = nullptr, .dependencyFlags = 0, .memoryBarrierCount = 1, .pMemoryBarriers = &barrier, .bufferMemoryBarrierCount = 0, .pBufferMemoryBarriers = nullptr, .imageMemoryBarrierCount = 0, .pImageMemoryBarriers = nullptr, }; vkCmdPipelineBarrier2(cmd, &dep); } }