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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
180 changes: 180 additions & 0 deletions AnimeStudio.ACLNative2/ACLLibrary.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#include "pch.h"
#include "ACLLibrary.h"

struct DatabaseSettings : public acl::default_database_settings
{
static constexpr acl::compressed_tracks_version16 version_supported() { return acl::compressed_tracks_version16::vHoYo; }
};

struct TransformDecompressionSettings : public acl::default_transform_decompression_settings
{
using database_settings_type = DatabaseSettings;
static constexpr acl::compressed_tracks_version16 version_supported() { return acl::compressed_tracks_version16::vHoYo; }
};

struct ScalarDecompressionSettings : public acl::default_hoyo_scalar_decompression_settings
{
using database_settings_type = DatabaseSettings;
static constexpr acl::compressed_tracks_version16 version_supported() { return acl::compressed_tracks_version16::vHoYo; }
// TODO: Fix this. The checks break because they try and cast the header to a transform_track
static constexpr bool skip_initialize_safety_checks() { return true; }
};

#define TRANSFORM_TRACK_SIZE 10

class TransformTrackWriter : public acl::track_writer
{
private:
int frame = 0;
uint32_t num_transform_tracks;
uint32_t num_scalar_tracks;
float* m_outputBuffer;

public:
TransformTrackWriter(float* outputBuffer, int num_transform_tracks, int num_scalar_tracks) : num_scalar_tracks(num_scalar_tracks), num_transform_tracks(num_transform_tracks), m_outputBuffer(outputBuffer) {}

void set_frame(int new_frame) {
frame = new_frame;
}

RTM_FORCE_INLINE void RTM_SIMD_CALL write_float1(uint32_t track_index, rtm::scalarf_arg0 value) {
float* frame_output_buffer = m_outputBuffer + ((TRANSFORM_TRACK_SIZE * num_transform_tracks) + num_scalar_tracks) * frame;
spdlog::debug("Write float {}", (void*)frame_output_buffer);
rtm::scalar_store(value, &frame_output_buffer[track_index]);
}

RTM_FORCE_INLINE void RTM_SIMD_CALL write_rotation(uint32_t track_index, rtm::quatf_arg0 rotation)
{
float* frame_output_buffer = m_outputBuffer + ((TRANSFORM_TRACK_SIZE * num_transform_tracks) + num_scalar_tracks) * frame;
spdlog::debug("Write rotation {}", (void*)frame_output_buffer);
rtm::quat_store(rotation, &frame_output_buffer[track_index * TRANSFORM_TRACK_SIZE]);
}

RTM_FORCE_INLINE void RTM_SIMD_CALL write_translation(uint32_t track_index, rtm::vector4f_arg0 translation)
{
float* frame_output_buffer = m_outputBuffer + ((TRANSFORM_TRACK_SIZE * num_transform_tracks) + num_scalar_tracks) * frame;
spdlog::debug("Write translation {}", (void*)frame_output_buffer);
rtm::vector_store3(translation, &frame_output_buffer[(track_index * TRANSFORM_TRACK_SIZE) + 4]);
}

RTM_FORCE_INLINE void RTM_SIMD_CALL write_scale(uint32_t track_index, rtm::vector4f_arg0 scale)
{
float* frame_output_buffer = m_outputBuffer + ((TRANSFORM_TRACK_SIZE * num_transform_tracks) + num_scalar_tracks) * frame;
spdlog::debug("Write scale {}", (void*)frame_output_buffer);
rtm::vector_store3(scale, &frame_output_buffer[(track_index * TRANSFORM_TRACK_SIZE) + 7]);
}
};

static acl::ansi_allocator ansi_allocator = acl::ansi_allocator();

void DecompressTracksZZZ(const acl::compressed_tracks* transform_tracks, const acl::compressed_tracks* scalar_tracks, const acl::compressed_database* database, const uint8_t* bulk_data, DecompressedClip* decompressedClip) {
acl::iallocator& allocator = ansi_allocator;

acl::database_context<DatabaseSettings> database_context;

if (transform_tracks != nullptr) {
spdlog::info("Decompressing transform tracks. stripped: {}", transform_tracks->has_database() ? "True" : "False");
}

if (scalar_tracks != nullptr) {
spdlog::info("Decompressing scalar tracks. stripped: {}", scalar_tracks->has_database() ? "True" : "False");
}

if (database != nullptr) {
if (database->is_bulk_data_inline()) {
spdlog::info("Initializing database with integral bulk data");
database_context.initialize(allocator, *database);
}
else if (bulk_data != nullptr) {
// TODO: This is slightly incorrect as the bulk data should be different between tiers. However, ZZZ only uses 1 tier, so it's not relevant for this special case.
// I suspect if this breaks in the future it will be because they move to using two tiers that are concatenated together in the stream attached to the AnimationClip.
ACL_ASSERT(database->get_bulk_data_size(acl::quality_tier::medium_importance) == 0, "Support for multiple streamers is not implemented.");
acl::null_database_streamer medium_streamer(bulk_data, database->get_bulk_data_size(acl::quality_tier::medium_importance));
acl::null_database_streamer low_streamer(bulk_data, database->get_bulk_data_size(acl::quality_tier::lowest_importance));
spdlog::info("Initializing database with stripped bulk data");
database_context.initialize(allocator, *database, medium_streamer, low_streamer);
// The lowest importance data is the highest fidelity data
database_context.stream_in(acl::quality_tier::lowest_importance);
}
else {
spdlog::error("A stripped database was provided, but bulk_data is null! Continuing without using the database...");
}
}

acl::decompression_context<TransformDecompressionSettings> transform_context;
if (transform_tracks != nullptr) {
spdlog::info("Initializing transform context from tracks: buf_sz: {}", transform_tracks->get_size());
if (transform_tracks->has_database() && database_context.is_initialized()) {
transform_context.initialize(*transform_tracks, database_context);
}
else {
transform_context.initialize(*transform_tracks);
}
}

acl::decompression_context<ScalarDecompressionSettings> scalar_context;
if (scalar_tracks != nullptr) {
spdlog::info("Initializing scalar context from tracks: buf_sz: {}", scalar_tracks->get_size());
if (scalar_tracks->has_database() && database_context.is_initialized()) {
scalar_context.initialize(*scalar_tracks, database_context);
}
else {
scalar_context.initialize(*scalar_tracks);
}
}
spdlog::info("Initialization done");

decompressedClip->Reset();
float step = 0.0f;
uint32_t num_transform_tracks = 0;
uint32_t num_scalar_tracks = 0;
if (transform_context.is_initialized()) {
ACL_ASSERT(transform_tracks != nullptr, "Transform context is initialized with null tracks!");
const auto num_samples = transform_tracks->get_num_samples_per_track();
const auto num_tracks = transform_tracks->get_num_tracks();
num_transform_tracks = num_tracks;
decompressedClip->TimesCount += num_samples;
decompressedClip->ValuesCount += 10 * num_samples * num_tracks;
step = 1.0f / transform_tracks->get_sample_rate();
}
if (scalar_context.is_initialized()) {
ACL_ASSERT(scalar_tracks != nullptr, "Scalar context is initialized with null tracks!");
const auto num_samples = scalar_tracks->get_num_samples_per_track();
const auto num_tracks = scalar_tracks->get_num_tracks();
num_scalar_tracks = num_tracks;
if (decompressedClip->TimesCount == 0) {
decompressedClip->TimesCount += num_samples;
}
decompressedClip->ValuesCount += num_samples * num_tracks;
if (step == 0.0f) {
step = 1.0f / scalar_tracks->get_sample_rate();
}
}

decompressedClip->Times = new float[decompressedClip->TimesCount] { 0.0f };
decompressedClip->Values = new float[decompressedClip->ValuesCount] { 0.0f };

TransformTrackWriter writer(decompressedClip->Values, num_transform_tracks, num_scalar_tracks);

spdlog::info("Decompressing {} frames of {} tracks ({} curves)...", decompressedClip->TimesCount, (num_transform_tracks * 3) + num_scalar_tracks, decompressedClip->ValuesCount);
for (int i = 0; i < decompressedClip->TimesCount; i++) {
spdlog::info("Decompressing frame {}", i);
float timestep = static_cast<float>(i) * step;
decompressedClip->Times[i] = timestep;
writer.set_frame(i);

if (transform_context.is_initialized()) {
transform_context.seek(timestep, acl::sample_rounding_policy::none);
transform_context.decompress_tracks(writer);
}

if (scalar_context.is_initialized()) {
// scalar_context.seek(timestep, acl::sample_rounding_policy::none);
// scalar_context.decompress_tracks(writer);
}
}
}

void Dispose(DecompressedClip* decompressedClip) {
decompressedClip->Dispose();
}
46 changes: 46 additions & 0 deletions AnimeStudio.ACLNative2/ACLLibrary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#ifdef ANIMESTUDIO.ACLNATIVE2_EXPORTS
#define ACLNATIVE_API __declspec(dllexport)
#else
#define ACLNATIVE_API __declspec(dllexport)
#endif

#define RTM_NO_DEPRECATION
#define ACL_ON_ASSERT_ABORT
#define FMT_UNICODE 0

#include "acl/core/ansi_allocator.h"
#include "acl/core/compressed_database.h"
#include "acl/core/compressed_tracks.h"
#include "acl/decompression/database/database.h"
#include "acl/decompression/database/database_settings.h"
#include "acl/decompression/database/database_streamer.h"
#include "acl/decompression/database/null_database_streamer.h"
#include "acl/decompression/decompress.h"
#include "acl/decompression/decompression_settings.h"
#include "spdlog/spdlog.h"

struct DecompressedClip {
float* Values;
int32_t ValuesCount;
float* Times;
int32_t TimesCount;

void Reset() {
Values = nullptr;
ValuesCount = 0;
Times = nullptr;
TimesCount = 0;
}

void Dispose() {
delete Values;
delete Times;
Reset();
}
};

// TODO: Make this work across x86 and x64 configurations.
// static_assert(sizeof(DecompressedClip) == 32, "DecompressedClip has incorrect size");

extern "C" ACLNATIVE_API void DecompressTracksZZZ(const acl::compressed_tracks* transform_tracks, const acl::compressed_tracks* scalar_tracks, const acl::compressed_database* database, const uint8_t* bulk_data, DecompressedClip* decompressedClip);
extern "C" ACLNATIVE_API void Dispose(DecompressedClip* decompressedClip);
99 changes: 99 additions & 0 deletions AnimeStudio.ACLNative2/AnimeStudio.ACLNative2.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// Language neutral resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU)
LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
#pragma code_page(65001)

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE
BEGIN
"resource.h\0"
END

2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END

3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END

#endif // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "FileDescription", "AnimeStudio.ACLNative2"
VALUE "FileVersion", "1.0.0.1"
VALUE "InternalName", "AnimeStudio.ACLNative2.dll"
VALUE "LegalCopyright", "Copyright (C) Escartem 2025; Copyright (C) hrothgar 2025"
VALUE "OriginalFilename", "AnimeStudio.ACLNative2.dll"
VALUE "ProductName", "AnimeStudio.ACLNative2"
VALUE "ProductVersion", "1.0.0.1"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1200
END
END

#endif // Language neutral resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

Loading