// File: crn_threaded_clusterizer.h // See Copyright Notice and license at the end of inc/crnlib.h #pragma once #include "crn_clusterizer.h" #include "crn_threading.h" namespace crnlib { template class threaded_clusterizer { CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(threaded_clusterizer); public: threaded_clusterizer(task_pool& tp) : m_pTask_pool(&tp), m_pProgress_callback(NULL), m_pProgress_callback_data(NULL), m_canceled(false) { } void clear() { for (uint i = 0; i < cMaxClusterizers; i++) m_clusterizers[i].clear(); } struct weighted_vec { weighted_vec() { } weighted_vec(const VectorType& v, uint w) : m_vec(v), m_weight(w) { } VectorType m_vec; uint m_weight; }; typedef crnlib::vector weighted_vec_array; typedef bool (*progress_callback_func)(uint percentage_completed, void* pProgress_data); bool create_clusters( const weighted_vec_array& weighted_vecs, uint max_clusters, crnlib::vector< crnlib::vector >& cluster_indices, progress_callback_func pProgress_callback, void* pProgress_callback_data) { m_main_thread_id = crn_get_current_thread_id(); m_canceled = false; m_pProgress_callback = pProgress_callback; m_pProgress_callback_data = pProgress_callback_data; if (max_clusters >= 128) { crnlib::vector primary_indices(weighted_vecs.size()); for (uint i = 0; i < weighted_vecs.size(); i++) primary_indices[i] = i; CRNLIB_ASSUME(cMaxClusterizers == 4); crnlib::vector indices[6]; compute_split(weighted_vecs, primary_indices, indices[0], indices[1]); compute_split(weighted_vecs, indices[0], indices[2], indices[3]); compute_split(weighted_vecs, indices[1], indices[4], indices[5]); create_clusters_task_state task_state[4]; m_cluster_task_displayed_progress = false; uint total_partitions = 0; for (uint i = 0; i < 4; i++) { const uint num_indices = indices[2 + i].size(); if (num_indices) total_partitions++; } for (uint i = 0; i < 4; i++) { const uint num_indices = indices[2 + i].size(); if (!num_indices) continue; task_state[i].m_pWeighted_vecs = &weighted_vecs; task_state[i].m_pIndices = &indices[2 + i]; task_state[i].m_max_clusters = (max_clusters + (total_partitions / 2)) / total_partitions; m_pTask_pool->queue_object_task(this, &threaded_clusterizer::create_clusters_task, i, &task_state[i]); } m_pTask_pool->join(); if (m_canceled) return false; uint total_clusters = 0; for (uint i = 0; i < 4; i++) total_clusters += task_state[i].m_cluster_indices.size(); cluster_indices.reserve(total_clusters); cluster_indices.resize(0); for (uint i = 0; i < 4; i++) { const uint ofs = cluster_indices.size(); cluster_indices.resize(ofs + task_state[i].m_cluster_indices.size()); for (uint j = 0; j < task_state[i].m_cluster_indices.size(); j++) { cluster_indices[ofs + j].swap( task_state[i].m_cluster_indices[j] ); } } } else { m_clusterizers[0].clear(); m_clusterizers[0].get_training_vecs().reserve(weighted_vecs.size()); for (uint i = 0; i < weighted_vecs.size(); i++) { const weighted_vec& v = weighted_vecs[i]; m_clusterizers[0].add_training_vec(v.m_vec, v.m_weight); } m_clusterizers[0].generate_codebook(max_clusters, generate_codebook_progress_callback, this, false);//m_params.m_dxt_quality <= cCRNDXTQualityFast); const uint num_clusters = m_clusterizers[0].get_codebook_size(); m_clusterizers[0].retrieve_clusters(num_clusters, cluster_indices); } return !m_canceled; } private: task_pool* m_pTask_pool; crn_thread_id_t m_main_thread_id; struct create_clusters_task_state { create_clusters_task_state() : m_pWeighted_vecs(NULL), m_pIndices(NULL), m_max_clusters(0) { } const weighted_vec_array* m_pWeighted_vecs; crnlib::vector* m_pIndices; crnlib::vector< crnlib::vector > m_cluster_indices; uint m_max_clusters; }; typedef clusterizer vector_clusterizer; enum { cMaxClusterizers = 4 }; vector_clusterizer m_clusterizers[cMaxClusterizers]; bool m_cluster_task_displayed_progress; progress_callback_func m_pProgress_callback; void* m_pProgress_callback_data; bool m_canceled; static bool generate_codebook_progress_callback(uint percentage_completed, void* pData) { threaded_clusterizer* pClusterizer = static_cast(pData); if (!pClusterizer->m_pProgress_callback) return true; if (!pClusterizer->m_pProgress_callback(percentage_completed, pClusterizer->m_pProgress_callback_data)) { pClusterizer->m_canceled = true; return false; } return true; } void compute_pca(VectorType& axis_res, VectorType& centroid_res, const weighted_vec_array& vecs, const vector& indices) { const uint N = VectorType::num_elements; VectorType centroid(0.0f); double total_weight = 0.0f; for (uint i = 0; i < indices.size(); i++) { const weighted_vec& v = vecs[indices[i]]; centroid += v.m_vec * static_cast(v.m_weight); total_weight += v.m_weight; } if (total_weight == 0.0f) { axis_res.clear(); centroid_res = centroid; return; } double one_over_total_weight = 1.0f / total_weight; for (uint i = 0; i < N; i++) centroid[i] = static_cast(centroid[i] * one_over_total_weight); matrix covar; covar.clear(); for (uint i = 0; i < indices.size(); i++) { const weighted_vec& weighted_vec = vecs[indices[i]]; const VectorType v(weighted_vec.m_vec - centroid); const VectorType w(v * static_cast(weighted_vec.m_weight)); for (uint x = 0; x < N; x++) for (uint y = x; y < N; y++) covar[x][y] = covar[x][y] + v[x] * w[y]; } for (uint x = 0; x < N; x++) for (uint y = x; y < N; y++) covar[x][y] = static_cast(covar[x][y] * one_over_total_weight); for (uint x = 0; x < (N - 1); x++) for (uint y = x + 1; y < N; y++) covar[y][x] = covar[x][y]; VectorType axis; for (uint i = 0; i < N; i++) axis[i] = math::lerp(.75f, 1.25f, i * (1.0f / (N - 1))); VectorType prev_axis(axis); const uint cMaxIterations = 10; for (uint iter = 0; iter < cMaxIterations; iter++) { VectorType x; double max_sum = 0; for (uint i = 0; i < N; i++) { double sum = 0; for (uint j = 0; j < N; j++) sum += axis[j] * covar[i][j]; x[i] = static_cast(sum); max_sum = math::maximum(max_sum, fabs(sum)); } if (max_sum != 0.0f) x *= static_cast(1.0f / max_sum); VectorType delta_axis(prev_axis - x); prev_axis = axis; axis = x; if (delta_axis.norm() < .0025f) break; } axis.normalize(); axis_res = axis; centroid_res = centroid; } void compute_division( const VectorType& axis, const VectorType& centroid, const weighted_vec_array& vecs, const vector& indices, vector& left_indices, vector& right_indices) { left_indices.resize(0); right_indices.resize(0); for (uint i = 0; i < indices.size(); i++) { const uint vec_index = indices[i]; const VectorType v(vecs[vec_index].m_vec - centroid); float t = v * axis; if (t < 0.0f) left_indices.push_back(vec_index); else right_indices.push_back(vec_index); } } void compute_split( const weighted_vec_array& vecs, const vector& indices, vector& left_indices, vector& right_indices) { VectorType axis, centroid; compute_pca(axis, centroid, vecs, indices); compute_division(axis, centroid, vecs, indices, left_indices, right_indices); } static bool generate_codebook_dummy_progress_callback(uint percentage_completed, void* pData) { percentage_completed; if (static_cast(pData)->m_canceled) return false; return true; } void create_clusters_task(uint64 data, void* pData_ptr) { if (m_canceled) return; const uint partition_index = static_cast(data); create_clusters_task_state& state = *static_cast(pData_ptr); m_clusterizers[partition_index].clear(); for (uint i = 0; i < state.m_pIndices->size(); i++) { const uint index = (*state.m_pIndices)[i]; const weighted_vec& v = (*state.m_pWeighted_vecs)[index]; m_clusterizers[partition_index].add_training_vec(v.m_vec, v.m_weight); } if (m_canceled) return; const bool is_main_thread = (crn_get_current_thread_id() == m_main_thread_id); const bool quick = false; m_clusterizers[partition_index].generate_codebook( state.m_max_clusters, (is_main_thread && !m_cluster_task_displayed_progress) ? generate_codebook_progress_callback : generate_codebook_dummy_progress_callback, this, quick); if (is_main_thread) m_cluster_task_displayed_progress = true; if (m_canceled) return; const uint num_clusters = m_clusterizers[partition_index].get_codebook_size(); m_clusterizers[partition_index].retrieve_clusters(num_clusters, state.m_cluster_indices); for (uint i = 0; i < state.m_cluster_indices.size(); i++) { crnlib::vector& indices = state.m_cluster_indices[i]; for (uint j = 0; j < indices.size(); j++) indices[j] = (*state.m_pIndices)[indices[j]]; } } }; } // namespace crnlib