Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions COPYRIGHT.txt
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,16 @@ Comment: Zstandard
Copyright: Meta Platforms, Inc. and affiliates.
License: BSD-3-clause

Files: thirdparty/zxc/*
Comment: ZXC
Copyright: 2025-2026, Bertrand Lebonnois and contributors.
License: BSD-3-clause

Files: thirdparty/zxc/src/lib/vendors/rapidhash.h
Comment: rapidhash
Copyright: 2025, Nicolas De Carli
License: Expat



License: Apache-2.0
Expand Down
4 changes: 4 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ opts.Add(
)
opts.Add(BoolVariable("minizip", "Enable ZIP archive support using minizip", True))
opts.Add(BoolVariable("brotli", "Enable Brotli for decompression and WOFF2 fonts support", True))
opts.Add(BoolVariable("zxc", "Enable the ZXC compression codec", True))
opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver on supported platforms", False))
opts.Add(BoolVariable("vulkan", "Enable the Vulkan rendering driver", True))
opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 rendering driver", True))
Expand Down Expand Up @@ -346,6 +347,7 @@ opts.Add(BoolVariable("builtin_rvo2_3d", "Use the built-in RVO2 3D library", Tru
opts.Add(BoolVariable("builtin_xatlas", "Use the built-in xatlas library", True))
opts.Add(BoolVariable("builtin_zlib", "Use the built-in zlib library", True))
opts.Add(BoolVariable("builtin_zstd", "Use the built-in Zstd library", True))
opts.Add(BoolVariable("builtin_zxc", "Use the built-in ZXC library", True))

# Compilation environment setup
# CXX, CC, and LINK directly set the equivalent `env` values (which may still
Expand Down Expand Up @@ -1094,6 +1096,8 @@ if env["minizip"]:
env.Append(CPPDEFINES=["MINIZIP_ENABLED"])
if env["brotli"]:
env.Append(CPPDEFINES=["BROTLI_ENABLED"])
if env["zxc"]:
env.Append(CPPDEFINES=["ZXC_ENABLED"])

if not env["disable_overrides"]:
env.Append(CPPDEFINES=["OVERRIDE_ENABLED"])
Expand Down
66 changes: 66 additions & 0 deletions core/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,72 @@ if env["builtin_zstd"]:

env_thirdparty.add_source_files(thirdparty_obj, thirdparty_zstd_sources)

# ZXC compression codec, can be unbundled
if env["zxc"] and env["builtin_zxc"]:
thirdparty_zxc_dir = "#thirdparty/zxc/"

# Include paths: public headers for <zxc.h>, plus the library's own internal
# and vendored headers needed by the thirdparty translation units.
env_thirdparty.Prepend(
CPPPATH=[
thirdparty_zxc_dir + "include",
thirdparty_zxc_dir + "src/lib",
thirdparty_zxc_dir + "src/lib/vendors",
]
)
# <zxc.h> must also be reachable from core/io/compression.cpp.
env.Prepend(CPPPATH=[thirdparty_zxc_dir + "include"])

env_thirdparty.Append(CPPDEFINES=["ZXC_STATIC_DEFINE"])
env.Append(CPPDEFINES=["ZXC_STATIC_DEFINE"])

# Sources compiled once, with no per-ISA variants and no special flags.
thirdparty_zxc_sources = [
"src/lib/zxc_common.c",
"src/lib/zxc_dispatch.c",
"src/lib/zxc_dict.c",
"src/lib/zxc_seekable.c",
"src/lib/zxc_pstream.c",
"src/lib/zxc_driver.c",
]
thirdparty_zxc_sources = [thirdparty_zxc_dir + file for file in thirdparty_zxc_sources]
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_zxc_sources)

# zxc_compress.c / zxc_decompress.c / zxc_huffman.c are each compiled once per
# CPU variant: a distinct ZXC_FUNCTION_SUFFIX mangles the exported symbols and
# the matching ISA flags enable the SIMD intrinsics for that translation unit.
# zxc_dispatch.c then selects the best variant at runtime (CPUID / getauxval),
# so a single binary built without -march=native still uses SIMD when present.
# The variant set must match the symbols referenced by zxc_dispatch.c for the
# target architecture (x86_64 -> sse2/avx2/avx512, arm -> neon, others -> none).
zxc_variant_sources = ["zxc_compress.c", "zxc_decompress.c", "zxc_huffman.c"]

# (suffix, gcc/clang flags, msvc flags). "_default" (scalar) is always built.
zxc_variants = [("_default", [], [])]
if env["arch"] == "x86_64":
zxc_variants += [
("_sse2", ["-msse2"], []), # x86_64 baseline; MSVC implies SSE2.
("_avx2", ["-mavx2", "-mfma", "-mbmi", "-mbmi2", "-mlzcnt"], ["/arch:AVX2"]),
("_avx512", ["-mavx512f", "-mavx512bw", "-mbmi", "-mbmi2", "-mlzcnt"], ["/arch:AVX512"]),
]
elif env["arch"] == "arm64":
# NEON is baseline on AArch64, so the intrinsics compile without extra flags.
zxc_variants += [("_neon", [], [])]
elif env["arch"] == "arm32":
zxc_variants += [("_neon", ["-mfpu=neon"], [])]

for suffix, gcc_flags, msvc_flags in zxc_variants:
env_zxc_variant = env_thirdparty.Clone()
env_zxc_variant.Append(CPPDEFINES=[("ZXC_FUNCTION_SUFFIX", suffix)])
variant_flags = msvc_flags if env.msvc else gcc_flags
if variant_flags:
env_zxc_variant.Append(CCFLAGS=variant_flags)
for source in zxc_variant_sources:
thirdparty_obj += env_zxc_variant.Object(
target=thirdparty_zxc_dir + "src/lib/" + source[:-2] + suffix,
source=thirdparty_zxc_dir + "src/lib/" + source,
)


env.core_sources += thirdparty_obj

Expand Down
3 changes: 3 additions & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,8 @@ Error ProjectSettings::setup(const String &p_path, const String &p_main_pack, bo

Compression::gzip_level = GLOBAL_GET("compression/formats/gzip/compression_level");

Compression::zxc_level = GLOBAL_GET("compression/formats/zxc/compression_level");

load_scene_groups_cache();

project_loaded = err == OK;
Expand Down Expand Up @@ -1798,6 +1800,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/zstd/window_log_size", PROPERTY_HINT_RANGE, "10,30,1"), Compression::zstd_window_log_size);
GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/zlib/compression_level", PROPERTY_HINT_RANGE, "-1,9,1"), Compression::zlib_level);
GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/gzip/compression_level", PROPERTY_HINT_RANGE, "-1,9,1"), Compression::gzip_level);
GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/zxc/compression_level", PROPERTY_HINT_RANGE, "1,6,1"), Compression::zxc_level);

GLOBAL_DEF("debug/settings/crash_handler/message",
String("Please include this when reporting the bug to the project developer."));
Expand Down
35 changes: 35 additions & 0 deletions core/io/compression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
#include <brotli/decode.h>
#endif

#ifdef ZXC_ENABLED
#include <zxc.h>
#endif

namespace {
struct ZstdDecompressorContext {
ZSTD_DCtx *zstd_d_ctx = nullptr;
Expand Down Expand Up @@ -124,6 +128,19 @@ int64_t Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int64_t p_sr
ZSTD_freeCCtx(cctx);
return (int64_t)ret;
} break;
case MODE_ZXC: {
#ifdef ZXC_ENABLED
zxc_compress_opts_t opts;
memset(&opts, 0, sizeof(opts));
opts.level = zxc_level;
const int64_t max_dst_size = get_max_compressed_buffer_size(p_src_size, MODE_ZXC);
const int64_t ret = zxc_compress(p_src, p_src_size, p_dst, max_dst_size, &opts);
ERR_FAIL_COND_V_MSG(ret < 0, -1, vformat("ZXC compression failed: %s.", zxc_error_name((int)ret)));
return ret;
#else
ERR_FAIL_V_MSG(-1, "Godot was compiled without ZXC support.");
#endif
} break;
}

ERR_FAIL_V(-1);
Expand Down Expand Up @@ -163,6 +180,13 @@ int64_t Compression::get_max_compressed_buffer_size(int64_t p_src_size, Mode p_m
case MODE_ZSTD: {
return ZSTD_compressBound(p_src_size);
} break;
case MODE_ZXC: {
#ifdef ZXC_ENABLED
return (int64_t)zxc_compress_bound(p_src_size);
#else
ERR_FAIL_V_MSG(-1, "Godot was compiled without ZXC support.");
#endif
} break;
}

ERR_FAIL_V(-1);
Expand Down Expand Up @@ -226,6 +250,17 @@ int64_t Compression::decompress(uint8_t *p_dst, int64_t p_dst_max_size, const ui
size_t ret = ZSTD_decompressDCtx(decompressor_ctx.zstd_d_ctx, p_dst, p_dst_max_size, p_src, p_src_size);
return (int64_t)ret;
} break;
case MODE_ZXC: {
#ifdef ZXC_ENABLED
zxc_decompress_opts_t opts;
memset(&opts, 0, sizeof(opts));
const int64_t ret = zxc_decompress(p_src, p_src_size, p_dst, p_dst_max_size, &opts);
ERR_FAIL_COND_V_MSG(ret < 0, -1, vformat("ZXC decompression failed: %s.", zxc_error_name((int)ret)));
return ret;
#else
ERR_FAIL_V_MSG(-1, "Godot was compiled without ZXC support.");
#endif
} break;
}

ERR_FAIL_V(-1);
Expand Down
4 changes: 3 additions & 1 deletion core/io/compression.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ class Compression {
static inline bool zstd_long_distance_matching = false;
static inline int zstd_window_log_size = 27; // ZSTD_WINDOWLOG_LIMIT_DEFAULT
static inline int gzip_chunk = 16384;
static inline int zxc_level = 3; // ZXC_LEVEL_DEFAULT

enum Mode : int32_t {
MODE_FASTLZ,
MODE_DEFLATE,
MODE_ZSTD,
MODE_GZIP,
MODE_BROTLI
MODE_BROTLI,
MODE_ZXC
};

static int64_t compress(uint8_t *p_dst, const uint8_t *p_src, int64_t p_src_size, Mode p_mode = MODE_ZSTD);
Expand Down
1 change: 1 addition & 0 deletions core/io/file_access.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,7 @@ void FileAccess::_bind_methods() {
BIND_ENUM_CONSTANT(COMPRESSION_ZSTD);
BIND_ENUM_CONSTANT(COMPRESSION_GZIP);
BIND_ENUM_CONSTANT(COMPRESSION_BROTLI);
BIND_ENUM_CONSTANT(COMPRESSION_ZXC);

BIND_BITFIELD_FLAG(UNIX_READ_OWNER);
BIND_BITFIELD_FLAG(UNIX_WRITE_OWNER);
Expand Down
1 change: 1 addition & 0 deletions core/io/file_access.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class FileAccess : public RefCounted {
COMPRESSION_ZSTD = Compression::MODE_ZSTD,
COMPRESSION_GZIP = Compression::MODE_GZIP,
COMPRESSION_BROTLI = Compression::MODE_BROTLI,
COMPRESSION_ZXC = Compression::MODE_ZXC,
};

typedef void (*FileCloseFailNotify)(const String &);
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/FileAccess.xml
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,9 @@
<constant name="COMPRESSION_BROTLI" value="4" enum="CompressionMode">
Uses the [url=https://github.com/google/brotli]brotli[/url] compression method (only decompression is supported).
</constant>
<constant name="COMPRESSION_ZXC" value="5" enum="CompressionMode">
Uses the ZXC compression method.
</constant>
<constant name="UNIX_READ_OWNER" value="256" enum="UnixPermissionFlags" is_bitfield="true">
Read for owner bit.
</constant>
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,9 @@
<member name="compression/formats/zstd/window_log_size" type="int" setter="" getter="" default="27">
Largest size limit (in power of 2) allowed when compressing using long-distance matching with Zstandard. Higher values can result in better compression, but will require more memory when compressing and decompressing.
</member>
<member name="compression/formats/zxc/compression_level" type="int" setter="" getter="" default="3">
The default compression level for ZXC. Affects compressed scenes and resources. Higher levels result in smaller files at the cost of compression speed. Decompression speed is mostly unaffected by the compression level.
</member>
<member name="debug/canvas_items/debug_redraw_color" type="Color" setter="" getter="" default="Color(1, 0.2, 0.2, 0.5)">
If canvas item redraw debugging is active, this color will be flashed on canvas items when they redraw.
</member>
Expand Down
53 changes: 53 additions & 0 deletions tests/core/io/test_file_access.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

TEST_FORCE_LINK(test_file_access)

#include "core/io/compression.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "tests/test_utils.h"
Expand Down Expand Up @@ -250,6 +251,58 @@ TEST_CASE("[FileAccess] Get/Store floating point half precision values") {
}
}

TEST_CASE("[Compression] ZXC round-trip") {
// Representative data: enough structure that it actually compresses.
PackedByteArray original;
original.resize(8192);
uint8_t *w = original.ptrw();
for (int i = 0; i < original.size(); i++) {
w[i] = uint8_t((i * 31 + (i >> 3)) & 0xFF);
}

// Compress via the Compression class (MODE_ZXC).
PackedByteArray compressed;
compressed.resize(Compression::get_max_compressed_buffer_size(original.size(), Compression::MODE_ZXC));
const int64_t compressed_size = Compression::compress(compressed.ptrw(), original.ptr(), original.size(), Compression::MODE_ZXC);
REQUIRE(compressed_size > 0);
compressed.resize(compressed_size);

// Decompress back into a buffer of the known original size.
PackedByteArray decompressed;
decompressed.resize(original.size());
const int64_t decompressed_size = Compression::decompress(decompressed.ptrw(), decompressed.size(), compressed.ptr(), compressed.size(), Compression::MODE_ZXC);
CHECK(decompressed_size == original.size());
CHECK(decompressed == original);
}

TEST_CASE("[FileAccess] ZXC compressed file round-trip") {
const String file_path = TestUtils::get_data_path("zxc_roundtrip.bin");

PackedByteArray original;
original.resize(4096);
uint8_t *w = original.ptrw();
for (int i = 0; i < original.size(); i++) {
w[i] = uint8_t((i * 17) & 0xFF);
}

{
Ref<FileAccess> fw = FileAccess::open_compressed(file_path, FileAccess::WRITE, FileAccess::COMPRESSION_ZXC);
REQUIRE(fw.is_valid());
fw->store_buffer(original);
fw->close();
}

{
Ref<FileAccess> fr = FileAccess::open_compressed(file_path, FileAccess::READ, FileAccess::COMPRESSION_ZXC);
REQUIRE(fr.is_valid());
const Vector<uint8_t> read_back = fr->get_buffer(original.size());
fr->close();
CHECK(read_back == original);
}

DirAccess::remove_file_or_error(file_path);
}

TEST_CASE("[FileAccess] Cursor positioning") {
Ref<FileAccess> f = FileAccess::open(TestUtils::get_data_path("line_endings_lf.test.txt"), FileAccess::READ);
REQUIRE(f.is_valid());
Expand Down
16 changes: 16 additions & 0 deletions thirdparty/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1311,3 +1311,19 @@ Files extracted from upstream source:

- `lib/{common/,compress/,decompress/,zstd.h,zstd_errors.h}`
- `LICENSE`


## zxc

- Upstream: https://github.com/hellobertrand/zxc
- Version: 0.12.0 (c8748471f7a6e895e4b9dc0d9063d91e8567c249, 2026)
- License: BSD-3-Clause

Files extracted from upstream source:

- `include/`
- `src/lib/` (including `src/lib/vendors/rapidhash.h`, MIT-licensed)
- `LICENSE`

The CMake/Meson build files, command-line tool, and test suite are not needed
and were not extracted.
64 changes: 64 additions & 0 deletions thirdparty/zxc/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
==============================================================================
ZXC
Copyright (c) 2025-2026, Bertrand Lebonnois and contributors
License: BSD 3-Clause
==============================================================================

BSD 3-Clause License
====================

Copyright (c) 2025-2026, Bertrand Lebonnois and contributors
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of ZXC nor the names of its contributors may be
used to endorse or promote products derived from this software
without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL BERTRAND LEBONNOIS OR CONTRIBUTORS BE LIABLE FOR
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

==============================================================================
This project includes code from rapidhash
Copyright (C) 2025 Nicolas De Carli
License: MIT (Expat)
==============================================================================

rapidhash - Very fast, high quality, platform-independent hashing algorithm.
Copyright (C) 2025 Nicolas De Carli

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.

You can contact the author at:
- rapidhash source repository: https://github.com/Nicoshev/rapidhash
Loading