Skip to content

Commit be07d07

Browse files
mklokockaGuekka
authored andcommittedOct 9, 2024
feat(tex): add crunch texture and functions #45
1 parent c58734f commit be07d07

14 files changed

+471
-3
lines changed
 

‎data/crunch.7z

109 KB
Binary file not shown.

‎data/crunch_compress_dxt1.7z

476 KB
Binary file not shown.

‎data/crunch_compress_dxt5.7z

982 KB
Binary file not shown.

‎data/crunch_mipmaps.7z

1.52 MB
Binary file not shown.

‎data/crunch_resize.7z

487 KB
Binary file not shown.

‎include/btu/tex/crunch_functions.hpp

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include "btu/tex/crunch_texture.hpp"
4+
#include "btu/tex/detail/common.hpp"
5+
#include "btu/tex/detail/formats_string.hpp"
6+
#include "btu/tex/dimension.hpp"
7+
8+
#include <crunch/crn_dxt_image.h>
9+
#include <crunch/crn_texture_conversion.h>
10+
#include <crunch/dds_defs.h>
11+
12+
namespace btu::tex {
13+
using crnlib::texture_conversion::convert_params;
14+
[[nodiscard]] auto resize(CrunchTexture &&file, Dimension dim) -> ResultCrunch;
15+
[[nodiscard]] auto generate_mipmaps(CrunchTexture &&file) -> ResultCrunch;
16+
[[nodiscard]] auto convert(CrunchTexture &&file, DXGI_FORMAT format) -> ResultCrunch;
17+
} // namespace btu::tex

‎include/btu/tex/crunch_texture.hpp

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* Copyright (C) 2021 Edgar B
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
5+
6+
#pragma once
7+
8+
#include "btu/common/path.hpp"
9+
#include "btu/tex/detail/common.hpp"
10+
11+
#include <crunch/crn_mipmapped_texture.h>
12+
13+
#include <filesystem>
14+
#include <span>
15+
16+
namespace crnlib {
17+
class mipmapped_texture;
18+
19+
auto operator==(const image_u8 &lhs, const image_u8 &rhs) noexcept -> bool;
20+
auto operator==(const mipmapped_texture &lhs, const mipmapped_texture &rhs) noexcept -> bool;
21+
} // namespace crnlib
22+
23+
namespace btu::tex {
24+
using crnlib::mipmapped_texture;
25+
26+
struct Dimension;
27+
enum class TextureType : std::uint8_t;
28+
29+
class CrunchTexture
30+
{
31+
public:
32+
void set(mipmapped_texture &&tex) noexcept;
33+
34+
[[nodiscard]] auto get() noexcept -> mipmapped_texture &;
35+
[[nodiscard]] auto get() const noexcept -> const mipmapped_texture &;
36+
37+
[[nodiscard]] auto get_dimension() const noexcept -> Dimension;
38+
39+
[[nodiscard]] auto get_load_path() const noexcept -> const Path &;
40+
void set_load_path(Path path) noexcept;
41+
42+
[[nodiscard]] auto get_texture_type() const noexcept -> const TextureType;
43+
44+
[[nodiscard]] auto operator==(const CrunchTexture &) const noexcept -> bool = default;
45+
46+
private:
47+
Path load_path_;
48+
mipmapped_texture tex_;
49+
};
50+
51+
[[nodiscard]] auto load_crunch(Path path) noexcept -> tl::expected<CrunchTexture, Error>;
52+
[[nodiscard]] auto load_crunch(Path relative_path,
53+
std::span<std::byte> data) noexcept -> tl::expected<CrunchTexture, Error>;
54+
55+
[[nodiscard]] auto save(const CrunchTexture &tex, const Path &path) noexcept -> ResultError;
56+
[[nodiscard]] auto save(const CrunchTexture &tex) noexcept -> tl::expected<std::vector<std::byte>, Error>;
57+
58+
} // namespace btu::tex

‎include/btu/tex/detail/common.hpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
namespace btu::tex {
1313
class Texture;
14+
class CrunchTexture;
1415

15-
using Result = tl::expected<Texture, Error>;
16-
using ResultError = tl::expected<void, Error>;
16+
using Result = tl::expected<Texture, Error>;
17+
using ResultCrunch = tl::expected<CrunchTexture, Error>;
18+
using ResultError = tl::expected<void, Error>;
1719
} // namespace btu::tex

‎src/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ set(HEADER_FILES
3434
"${INCLUDE_DIR}/btu/tex/functions.hpp"
3535
"${INCLUDE_DIR}/btu/tex/optimize.hpp"
3636
"${INCLUDE_DIR}/btu/tex/texture.hpp"
37+
"${INCLUDE_DIR}/btu/tex/crunch_texture.hpp"
38+
"${INCLUDE_DIR}/btu/tex/crunch_functions.hpp"
3739
"${INCLUDE_DIR}/btu/tex/detail/common.hpp"
3840
"${INCLUDE_DIR}/btu/tex/detail/formats_string.hpp"
3941
../include/btu/common/json.hpp)
@@ -59,6 +61,8 @@ set(SOURCE_FILES
5961
"${SOURCE_DIR}/tex/functions_compress_bc7.cpp"
6062
"${SOURCE_DIR}/tex/optimize.cpp"
6163
"${SOURCE_DIR}/tex/texture.cpp"
64+
"${SOURCE_DIR}/tex/crunch_texture.cpp"
65+
"${SOURCE_DIR}/tex/crunch_functions.cpp"
6266
"${SOURCE_DIR}/tex/error_code.cpp")
6367

6468
source_group(TREE "${ROOT_DIR}" FILES ${HEADER_FILES} ${SOURCE_FILES})
@@ -106,6 +110,9 @@ target_link_libraries("${PROJECT_NAME}" PRIVATE nlohmann_json::nlohmann_json)
106110
find_package(reproc++ CONFIG REQUIRED)
107111
target_link_libraries("${PROJECT_NAME}" PRIVATE reproc++)
108112

113+
find_package(crunch2 CONFIG REQUIRED)
114+
target_link_libraries("${PROJECT_NAME}" PRIVATE crunch::crunch2)
115+
109116
set_target_properties("${PROJECT_NAME}" PROPERTIES DEBUG_POSTFIX "d")
110117
set_target_properties("${PROJECT_NAME}" PROPERTIES LINKER_LANGUAGE CXX)
111118

‎src/tex/crunch_functions.cpp

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#include <btu/tex/crunch_functions.hpp>
2+
#include <btu/tex/formats.hpp>
3+
#include <crunch/crnlib.h>
4+
5+
#include <algorithm>
6+
#include <thread>
7+
8+
namespace btu::tex {
9+
using crnlib::dxt_image;
10+
using crnlib::pixel_format;
11+
12+
void set_gamma_correction(mipmapped_texture::resample_params &params, const TextureType tex_type)
13+
{
14+
// Diffuse textures are assumed to be sRGB.
15+
if (tex_type == TextureType::Diffuse)
16+
{
17+
params.m_gamma = 2.2f;
18+
params.m_srgb = true;
19+
}
20+
else
21+
{
22+
params.m_gamma = 1.0f;
23+
params.m_srgb = false;
24+
}
25+
}
26+
27+
auto resize(CrunchTexture &&file, Dimension dim) -> ResultCrunch
28+
{
29+
// Resizes the input texture. If compressed, automatically decompresses it. Removes mipmaps.
30+
mipmapped_texture::resample_params res_params;
31+
res_params.m_filter_scale = 1.0f;
32+
res_params.m_multithreaded = true;
33+
34+
set_gamma_correction(res_params, file.get_texture_type());
35+
36+
const auto success = file.get().resize(dim.w, dim.h, res_params);
37+
if (!success)
38+
{
39+
// TODO: Better error propagation.
40+
return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
41+
}
42+
43+
return std::move(file);
44+
}
45+
46+
auto generate_mipmaps(CrunchTexture &&file) -> ResultCrunch
47+
{
48+
mipmapped_texture::generate_mipmap_params gen_params;
49+
gen_params.m_multithreaded = true;
50+
gen_params.m_max_mips = crn_limits::cCRNMaxLevels;
51+
gen_params.m_min_mip_size = 1;
52+
53+
set_gamma_correction(gen_params, file.get_texture_type());
54+
55+
const auto success = file.get().generate_mipmaps(gen_params, true);
56+
if (!success)
57+
{
58+
// TODO: Better error propagation.
59+
return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
60+
}
61+
62+
return std::move(file);
63+
}
64+
65+
auto convert(CrunchTexture &&file, DXGI_FORMAT format) -> ResultCrunch
66+
{
67+
pixel_format crunch_format;
68+
switch (format)
69+
{
70+
case DXGI_FORMAT_BC1_UNORM: crunch_format = pixel_format::PIXEL_FMT_DXT1; break;
71+
case DXGI_FORMAT_BC3_UNORM: crunch_format = pixel_format::PIXEL_FMT_DXT5; break;
72+
case DXGI_FORMAT_B8G8R8X8_UNORM: crunch_format = pixel_format::PIXEL_FMT_R8G8B8; break;
73+
case DXGI_FORMAT_B8G8R8A8_UNORM:
74+
case DXGI_FORMAT_R8G8B8A8_UNORM: crunch_format = pixel_format::PIXEL_FMT_A8R8G8B8; break;
75+
default: return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
76+
}
77+
78+
// Default compression arguments.
79+
dxt_image::pack_params pack_params;
80+
81+
// Crunch is capped to 16 threads total, but it should be handled internally.
82+
const auto threads = std::thread::hardware_concurrency();
83+
pack_params.m_num_helper_threads = (threads ? threads : 1) - 1;
84+
85+
// Disable endpoint caching for best and deterministic results. Time savings are nearly non-existent.
86+
pack_params.m_endpoint_caching = false;
87+
88+
pack_params.m_perceptual = file.get_texture_type() == TextureType::Diffuse;
89+
90+
const auto success = file.get().convert(crunch_format, pack_params);
91+
if (!success)
92+
{
93+
// TODO: Better error propagation.
94+
return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
95+
}
96+
97+
return std::move(file);
98+
}
99+
} // namespace btu::tex

‎src/tex/crunch_texture.cpp

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/* Copyright (C) 2021 Edgar B
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
5+
6+
#include <btu/common/string.hpp>
7+
#include <btu/tex/crunch_texture.hpp>
8+
#include <btu/tex/dimension.hpp>
9+
#include <btu/tex/formats.hpp>
10+
#include <crunch/crn_cfile_stream.h>
11+
#include <crunch/crn_core.h>
12+
#include <crunch/crn_dynamic_stream.h>
13+
#include <crunch/crn_image.h>
14+
#include <crunch/crn_mipmapped_texture.h>
15+
#include <winerror.h>
16+
17+
#include <mutex>
18+
19+
namespace crnlib {
20+
auto operator==(const image_u8 &lhs, const image_u8 &rhs) noexcept -> bool
21+
{
22+
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
23+
auto *lhs_end = lhs.get_ptr() + lhs.get_total();
24+
auto *rhs_end = rhs.get_ptr() + rhs.get_total();
25+
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
26+
return std::equal(lhs.get_ptr(), lhs_end, rhs.get_ptr(), rhs_end);
27+
}
28+
29+
auto operator==(const mipmapped_texture &lhs, const mipmapped_texture &rhs) noexcept -> bool
30+
{
31+
auto cmp = [&](auto... ptrs) { return ((std::invoke(ptrs, lhs) == std::invoke(ptrs, rhs)) && ...); };
32+
const bool first = cmp(&mipmapped_texture::get_num_faces,
33+
&mipmapped_texture::get_num_levels,
34+
&mipmapped_texture::get_total_pixels);
35+
if (!first)
36+
return false;
37+
38+
image_u8 lhs_temp, rhs_temp;
39+
for (uint face = 0; face < lhs.get_num_faces(); face++)
40+
{
41+
for (uint level = 0; level < lhs.get_num_levels(); level++)
42+
{
43+
image_u8 *lhs_img = lhs.get_level_image(face, level, lhs_temp);
44+
image_u8 *rhs_img = rhs.get_level_image(face, level, rhs_temp);
45+
if (*lhs_img != *rhs_img)
46+
return false;
47+
}
48+
}
49+
50+
return true;
51+
}
52+
} // namespace crnlib
53+
54+
namespace btu::tex {
55+
using namespace crnlib;
56+
57+
void CrunchTexture::set(mipmapped_texture &&tex) noexcept
58+
{
59+
tex_ = std::move(tex);
60+
}
61+
62+
auto CrunchTexture::get() noexcept -> mipmapped_texture &
63+
{
64+
return tex_;
65+
}
66+
67+
auto CrunchTexture::get() const noexcept -> const mipmapped_texture &
68+
{
69+
return tex_;
70+
}
71+
72+
auto CrunchTexture::get_dimension() const noexcept -> Dimension
73+
{
74+
return {tex_.get_width(), tex_.get_height()};
75+
}
76+
77+
auto CrunchTexture::get_load_path() const noexcept -> const Path &
78+
{
79+
return load_path_;
80+
}
81+
82+
void CrunchTexture::set_load_path(Path path) noexcept
83+
{
84+
load_path_ = std::move(path);
85+
}
86+
87+
auto CrunchTexture::get_texture_type() const noexcept -> const TextureType
88+
{
89+
const auto filename = load_path_.filename().u8string();
90+
91+
const auto guessed = guess_texture_type(filename);
92+
93+
return guessed.value_or(TextureType::Diffuse);
94+
}
95+
96+
auto load_crunch(Path path) noexcept -> tl::expected<CrunchTexture, Error>
97+
{
98+
CrunchTexture tex;
99+
tex.set_load_path(std::move(path));
100+
101+
mipmapped_texture tex_;
102+
103+
const auto load_path = tex.get_load_path().string();
104+
105+
texture_file_types::format src_file_format = texture_file_types::determine_file_format(load_path.c_str());
106+
107+
const auto success = tex_.read_from_file(load_path.c_str(), src_file_format);
108+
if (!success)
109+
{
110+
// Crunch saves loading errors in the texture object as a string.
111+
// Also it is being cleared in a weird fashion so not super helpful.
112+
return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
113+
}
114+
115+
tex.set(std::move(tex_));
116+
117+
return tex;
118+
}
119+
120+
auto load_crunch(Path relative_path, std::span<std::byte> data) noexcept -> tl::expected<CrunchTexture, Error>
121+
{
122+
CrunchTexture tex;
123+
tex.set_load_path(std::move(relative_path));
124+
125+
const auto load_path = tex.get_load_path().string();
126+
127+
mipmapped_texture tex_;
128+
dynamic_stream in_stream(data.data(), data.size(), load_path.c_str());
129+
data_stream_serializer serializer(in_stream);
130+
const auto success = tex_.read_from_stream(serializer);
131+
if (!success)
132+
{
133+
// Crunch saves loading errors in the texture object as a string.
134+
// Also it is being cleared in a weird fashion so not super helpful.
135+
return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
136+
}
137+
138+
tex.set(std::move(tex_));
139+
140+
return tex;
141+
}
142+
143+
auto save(const CrunchTexture &tex, const Path &path) noexcept -> ResultError
144+
{
145+
const auto filename = path.string();
146+
147+
cfile_stream write_stream;
148+
if (!write_stream.open(filename.c_str(), cDataStreamWritable | cDataStreamSeekable))
149+
{
150+
return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
151+
}
152+
data_stream_serializer serializer(write_stream);
153+
154+
if (!tex.get().write_dds(serializer))
155+
return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
156+
return {};
157+
}
158+
159+
auto save(const CrunchTexture &tex) noexcept -> tl::expected<std::vector<std::byte>, Error>
160+
{
161+
dynamic_stream out_stream;
162+
data_stream_serializer serializer(out_stream);
163+
164+
if (!tex.get().write_dds(serializer))
165+
return tl::make_unexpected(error_from_hresult(E_UNEXPECTED));
166+
167+
auto buf = out_stream.get_buf();
168+
// NOLINTBEGIN(*pointer-arithmetic): needed for the conversion to work properly
169+
return std::vector(static_cast<std::byte *>(static_cast<void *>(buf.get_ptr())),
170+
static_cast<std::byte *>(static_cast<void *>(buf.get_ptr())) + buf.size_in_bytes());
171+
// NOLINTEND(*pointer-arithmetic)
172+
}
173+
174+
} // namespace btu::tex

‎tests/CMakeLists.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ set(SOURCE_FILES
2525
"${SOURCE_DIR}/nif/utils.hpp"
2626
"${SOURCE_DIR}/tex/formats.cpp"
2727
"${SOURCE_DIR}/tex/functions.cpp"
28-
"${SOURCE_DIR}/tex/optimize.cpp")
28+
"${SOURCE_DIR}/tex/optimize.cpp"
29+
"${SOURCE_DIR}/tex/crunch.cpp")
2930

3031
if("${BETHUTIL_BUILD_EXAMPLES}")
3132
list(APPEND SOURCE_FILES)
@@ -84,6 +85,11 @@ acquire_test("nif_corrupted")
8485
acquire_test("read_file")
8586
acquire_test("nif_memory_io")
8687
acquire_test("tex_memory_io")
88+
acquire_test("crunch")
89+
acquire_test("crunch_resize")
90+
acquire_test("crunch_mipmaps")
91+
acquire_test("crunch_compress_dxt1")
92+
acquire_test("crunch_compress_dxt5")
8793

8894
if("${BETHUTIL_BUILD_EXAMPLES}")
8995
target_include_directories(tests PRIVATE "${ROOT_DIR}/examples")

‎tests/tex/crunch.cpp

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include "./utils.hpp"
2+
3+
#include <btu/common/filesystem.hpp>
4+
#include <btu/tex/crunch_functions.hpp>
5+
#include <btu/tex/crunch_texture.hpp>
6+
7+
#include <filesystem>
8+
9+
inline auto load_tex_crunch(const Path &path) -> btu::tex::CrunchTexture
10+
{
11+
auto res = btu::tex::load_crunch(path);
12+
INFO(path);
13+
REQUIRE(res.has_value());
14+
return std::move(res).value();
15+
}
16+
17+
template<typename Func>
18+
auto test_expected_crunch(const Path &root, const Path &filename, Func f)
19+
{
20+
auto in = load_tex_crunch(root / "in" / filename);
21+
const btu::tex::ResultCrunch out = f(std::move(in));
22+
23+
INFO("Processing: " << filename);
24+
if (!out)
25+
{
26+
FAIL_CHECK("Error: " << out.error());
27+
}
28+
29+
const auto expected_path = root / "expected" / filename;
30+
const auto expected = load_tex_crunch(expected_path);
31+
INFO("Expected path: " << expected_path);
32+
33+
CHECK(out.value().get() == expected.get());
34+
}
35+
36+
template<typename Func>
37+
void test_expected_dir_crunch(const Path &root, const Func &f)
38+
{
39+
const auto in_dir = root / "in";
40+
for (const auto &file : btu::fs::recursive_directory_iterator(in_dir))
41+
if (file.is_regular_file())
42+
test_expected_crunch(root, file.path().lexically_relative(in_dir), f);
43+
}
44+
45+
using btu::tex::Dimension, btu::tex::CrunchTexture;
46+
47+
TEST_CASE("Crunch Tex Memory IO", "[src]")
48+
{
49+
const auto dir = Path{"crunch"};
50+
const auto file = dir / "in" / u8"rock.dds";
51+
52+
// load
53+
auto data = require_expected(btu::common::read_file(file));
54+
auto mem_tex = require_expected(btu::tex::load_crunch(file, data));
55+
auto fs_tex = require_expected(btu::tex::load_crunch(file));
56+
57+
// test that loaded tex has the correct dimension
58+
REQUIRE(fs_tex.get_dimension() == Dimension{256, 256});
59+
60+
REQUIRE(mem_tex == fs_tex);
61+
62+
// save
63+
auto mem_data = btu::tex::save(mem_tex);
64+
REQUIRE(mem_data.has_value());
65+
66+
auto out = dir / "out" / u8"tex.dds";
67+
btu::fs::create_directories(out.parent_path());
68+
69+
REQUIRE(btu::tex::save(mem_tex, out));
70+
71+
auto fs_data = btu::common::read_file(out);
72+
73+
REQUIRE(*mem_data == fs_data);
74+
}
75+
76+
TEST_CASE("Crunch resize", "[src]")
77+
{
78+
test_expected_dir_crunch(u8"crunch_resize", [](auto &&tex) {
79+
constexpr auto args = btu::tex::util::ResizeRatio{3, {200, 200}};
80+
const auto dim = btu::tex::util::compute_resize_dimension(tex.get_dimension(), args);
81+
return btu::tex::resize(std::forward<decltype(tex)>(tex), dim);
82+
});
83+
}
84+
85+
TEST_CASE("Crunch mipmaps", "[src]")
86+
{
87+
test_expected_dir_crunch(u8"crunch_mipmaps", btu::tex::generate_mipmaps);
88+
}
89+
90+
TEST_CASE("Crunch convert", "[src]")
91+
{
92+
SECTION("dxt1")
93+
{
94+
test_expected_dir_crunch(u8"crunch_compress_dxt1", [](auto &&tex) {
95+
return btu::tex::convert(std::forward<decltype(tex)>(tex), DXGI_FORMAT_BC1_UNORM);
96+
});
97+
}
98+
SECTION("dxt5")
99+
{
100+
test_expected_dir_crunch(u8"crunch_compress_dxt5", [](auto &&tex) {
101+
return btu::tex::convert(std::forward<decltype(tex)>(tex), DXGI_FORMAT_BC3_UNORM);
102+
});
103+
}
104+
}

‎tests/tex/utils.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "../utils.hpp"
99
#include "btu/tex/compression_device.hpp"
10+
#include "btu/tex/crunch_texture.hpp"
1011
#include "btu/tex/dimension.hpp"
1112
#include "btu/tex/texture.hpp"
1213

0 commit comments

Comments
 (0)
Please sign in to comment.