| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844 | // basisu_resampler.cpp// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at////    http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#include "basisu_resampler.h"#include "basisu_resampler_filters.h"#define RESAMPLER_DEBUG 0namespace basisu{	static inline int resampler_range_check(int v, int h)	{		BASISU_NOTE_UNUSED(h);		assert((v >= 0) && (v < h));		return v;	}	// Float to int cast with truncation.	static inline int cast_to_int(Resample_Real i)	{		return (int)i;	}	// Ensure that the contributing source sample is within bounds. If not, reflect, clamp, or wrap.	int Resampler::reflect(const int j, const int src_x, const Boundary_Op boundary_op)	{		int n;		if (j < 0)		{			if (boundary_op == BOUNDARY_REFLECT)			{				n = -j;				if (n >= src_x)					n = src_x - 1;			}			else if (boundary_op == BOUNDARY_WRAP)				n = posmod(j, src_x);			else				n = 0;		}		else if (j >= src_x)		{			if (boundary_op == BOUNDARY_REFLECT)			{				n = (src_x - j) + (src_x - 1);				if (n < 0)					n = 0;			}			else if (boundary_op == BOUNDARY_WRAP)				n = posmod(j, src_x);			else				n = src_x - 1;		}		else			n = j;		return n;	}	// The make_clist() method generates, for all destination samples,	// the list of all source samples with non-zero weighted contributions.	Resampler::Contrib_List * Resampler::make_clist(		int src_x, int dst_x, Boundary_Op boundary_op,		Resample_Real(*Pfilter)(Resample_Real),		Resample_Real filter_support,		Resample_Real filter_scale,		Resample_Real src_ofs)	{		struct Contrib_Bounds		{			// The center of the range in DISCRETE coordinates (pixel center = 0.0f).			Resample_Real center;			int left, right;		};		int i, j, k, n, left, right;		Resample_Real total_weight;		Resample_Real xscale, center, half_width, weight;		Contrib_List* Pcontrib;		Contrib* Pcpool;		Contrib* Pcpool_next;		Contrib_Bounds* Pcontrib_bounds;		if ((Pcontrib = (Contrib_List*)calloc(dst_x, sizeof(Contrib_List))) == NULL)			return NULL;		Pcontrib_bounds = (Contrib_Bounds*)calloc(dst_x, sizeof(Contrib_Bounds));		if (!Pcontrib_bounds)		{			free(Pcontrib);			return (NULL);		}		const Resample_Real oo_filter_scale = 1.0f / filter_scale;		const Resample_Real NUDGE = 0.5f;		xscale = dst_x / (Resample_Real)src_x;		if (xscale < 1.0f)		{			int total;			(void)total;			// Handle case when there are fewer destination samples than source samples (downsampling/minification).			// stretched half width of filter			half_width = (filter_support / xscale) * filter_scale;			// Find the range of source sample(s) that will contribute to each destination sample.			for (i = 0, n = 0; i < dst_x; i++)			{				// Convert from discrete to continuous coordinates, scale, then convert back to discrete.				center = ((Resample_Real)i + NUDGE) / xscale;				center -= NUDGE;				center += src_ofs;				left = cast_to_int((Resample_Real)floor(center - half_width));				right = cast_to_int((Resample_Real)ceil(center + half_width));				Pcontrib_bounds[i].center = center;				Pcontrib_bounds[i].left = left;				Pcontrib_bounds[i].right = right;				n += (right - left + 1);			}			// Allocate memory for contributors. 			if ((n == 0) || ((Pcpool = (Contrib*)calloc(n, sizeof(Contrib))) == NULL))			{				free(Pcontrib);				free(Pcontrib_bounds);				return NULL;			}			total = n;			Pcpool_next = Pcpool;			// Create the list of source samples which contribute to each destination sample.			for (i = 0; i < dst_x; i++)			{				int max_k = -1;				Resample_Real max_w = -1e+20f;				center = Pcontrib_bounds[i].center;				left = Pcontrib_bounds[i].left;				right = Pcontrib_bounds[i].right;				Pcontrib[i].n = 0;				Pcontrib[i].p = Pcpool_next;				Pcpool_next += (right - left + 1);				assert((Pcpool_next - Pcpool) <= total);				total_weight = 0;				for (j = left; j <= right; j++)					total_weight += (*Pfilter)((center - (Resample_Real)j) * xscale * oo_filter_scale);				const Resample_Real norm = static_cast<Resample_Real>(1.0f / total_weight);				total_weight = 0;#if RESAMPLER_DEBUG				printf("%i: ", i);#endif				for (j = left; j <= right; j++)				{					weight = (*Pfilter)((center - (Resample_Real)j) * xscale * oo_filter_scale) * norm;					if (weight == 0.0f)						continue;					n = reflect(j, src_x, boundary_op);#if RESAMPLER_DEBUG					printf("%i(%f), ", n, weight);#endif					// Increment the number of source samples which contribute to the current destination sample.					k = Pcontrib[i].n++;					Pcontrib[i].p[k].pixel = (unsigned short)n; /* store src sample number */					Pcontrib[i].p[k].weight = weight;           /* store src sample weight */					total_weight += weight; /* total weight of all contributors */					if (weight > max_w)					{						max_w = weight;						max_k = k;					}				}#if RESAMPLER_DEBUG				printf("\n\n");#endif				//assert(Pcontrib[i].n);				//assert(max_k != -1);				if ((max_k == -1) || (Pcontrib[i].n == 0))				{					free(Pcpool);					free(Pcontrib);					free(Pcontrib_bounds);					return NULL;				}				if (total_weight != 1.0f)					Pcontrib[i].p[max_k].weight += 1.0f - total_weight;			}		}		else		{			// Handle case when there are more destination samples than source samples (upsampling).			half_width = filter_support * filter_scale;			// Find the source sample(s) that contribute to each destination sample.			for (i = 0, n = 0; i < dst_x; i++)			{				// Convert from discrete to continuous coordinates, scale, then convert back to discrete.				center = ((Resample_Real)i + NUDGE) / xscale;				center -= NUDGE;				center += src_ofs;				left = cast_to_int((Resample_Real)floor(center - half_width));				right = cast_to_int((Resample_Real)ceil(center + half_width));				Pcontrib_bounds[i].center = center;				Pcontrib_bounds[i].left = left;				Pcontrib_bounds[i].right = right;				n += (right - left + 1);			}			/* Allocate memory for contributors. */			int total = n;			if ((total == 0) || ((Pcpool = (Contrib*)calloc(total, sizeof(Contrib))) == NULL))			{				free(Pcontrib);				free(Pcontrib_bounds);				return NULL;			}			Pcpool_next = Pcpool;			// Create the list of source samples which contribute to each destination sample.			for (i = 0; i < dst_x; i++)			{				int max_k = -1;				Resample_Real max_w = -1e+20f;				center = Pcontrib_bounds[i].center;				left = Pcontrib_bounds[i].left;				right = Pcontrib_bounds[i].right;				Pcontrib[i].n = 0;				Pcontrib[i].p = Pcpool_next;				Pcpool_next += (right - left + 1);				assert((Pcpool_next - Pcpool) <= total);				total_weight = 0;				for (j = left; j <= right; j++)					total_weight += (*Pfilter)((center - (Resample_Real)j) * oo_filter_scale);				const Resample_Real norm = static_cast<Resample_Real>(1.0f / total_weight);				total_weight = 0;#if RESAMPLER_DEBUG				printf("%i: ", i);#endif				for (j = left; j <= right; j++)				{					weight = (*Pfilter)((center - (Resample_Real)j) * oo_filter_scale) * norm;					if (weight == 0.0f)						continue;					n = reflect(j, src_x, boundary_op);#if RESAMPLER_DEBUG					printf("%i(%f), ", n, weight);#endif					// Increment the number of source samples which contribute to the current destination sample.					k = Pcontrib[i].n++;					Pcontrib[i].p[k].pixel = (unsigned short)n; /* store src sample number */					Pcontrib[i].p[k].weight = weight;           /* store src sample weight */					total_weight += weight; /* total weight of all contributors */					if (weight > max_w)					{						max_w = weight;						max_k = k;					}				}#if RESAMPLER_DEBUG				printf("\n\n");#endif				//assert(Pcontrib[i].n);				//assert(max_k != -1);				if ((max_k == -1) || (Pcontrib[i].n == 0))				{					free(Pcpool);					free(Pcontrib);					free(Pcontrib_bounds);					return NULL;				}				if (total_weight != 1.0f)					Pcontrib[i].p[max_k].weight += 1.0f - total_weight;			}		}#if RESAMPLER_DEBUG		printf("*******\n");#endif		free(Pcontrib_bounds);		return Pcontrib;	}	void Resampler::resample_x(Sample * Pdst, const Sample * Psrc)	{		assert(Pdst);		assert(Psrc);		int i, j;		Sample total;		Contrib_List* Pclist = m_Pclist_x;		Contrib* p;		for (i = m_resample_dst_x; i > 0; i--, Pclist++)		{#if BASISU_RESAMPLER_DEBUG_OPS			total_ops += Pclist->n;#endif			for (j = Pclist->n, p = Pclist->p, total = 0; j > 0; j--, p++)				total += Psrc[p->pixel] * p->weight;			*Pdst++ = total;		}	}	void Resampler::scale_y_mov(Sample * Ptmp, const Sample * Psrc, Resample_Real weight, int dst_x)	{		int i;#if BASISU_RESAMPLER_DEBUG_OPS		total_ops += dst_x;#endif		// Not += because temp buf wasn't cleared.		for (i = dst_x; i > 0; i--)			* Ptmp++ = *Psrc++ * weight;	}	void Resampler::scale_y_add(Sample * Ptmp, const Sample * Psrc, Resample_Real weight, int dst_x)	{#if BASISU_RESAMPLER_DEBUG_OPS		total_ops += dst_x;#endif		for (int i = dst_x; i > 0; i--)			(*Ptmp++) += *Psrc++ * weight;	}	void Resampler::clamp(Sample * Pdst, int n)	{		while (n > 0)		{			Sample x = *Pdst;			*Pdst++ = clamp_sample(x);			n--;		}	}	void Resampler::resample_y(Sample * Pdst)	{		int i, j;		Sample* Psrc;		Contrib_List* Pclist = &m_Pclist_y[m_cur_dst_y];		Sample* Ptmp = m_delay_x_resample ? m_Ptmp_buf : Pdst;		assert(Ptmp);		/* Process each contributor. */		for (i = 0; i < Pclist->n; i++)		{			// locate the contributor's location in the scan buffer -- the contributor must always be found!			for (j = 0; j < MAX_SCAN_BUF_SIZE; j++)				if (m_Pscan_buf->scan_buf_y[j] == Pclist->p[i].pixel)					break;			assert(j < MAX_SCAN_BUF_SIZE);			Psrc = m_Pscan_buf->scan_buf_l[j];			if (!i)				scale_y_mov(Ptmp, Psrc, Pclist->p[i].weight, m_intermediate_x);			else				scale_y_add(Ptmp, Psrc, Pclist->p[i].weight, m_intermediate_x);			/* If this source line doesn't contribute to any			* more destination lines then mark the scanline buffer slot			* which holds this source line as free.			* (The max. number of slots used depends on the Y			* axis sampling factor and the scaled filter width.)			*/			if (--m_Psrc_y_count[resampler_range_check(Pclist->p[i].pixel, m_resample_src_y)] == 0)			{				m_Psrc_y_flag[resampler_range_check(Pclist->p[i].pixel, m_resample_src_y)] = false;				m_Pscan_buf->scan_buf_y[j] = -1;			}		}		/* Now generate the destination line */		if (m_delay_x_resample) // Was X resampling delayed until after Y resampling?		{			assert(Pdst != Ptmp);			resample_x(Pdst, Ptmp);		}		else		{			assert(Pdst == Ptmp);		}		if (m_lo < m_hi)			clamp(Pdst, m_resample_dst_x);	}	bool Resampler::put_line(const Sample * Psrc)	{		int i;		if (m_cur_src_y >= m_resample_src_y)			return false;		/* Does this source line contribute		* to any destination line? if not,		* exit now.		*/		if (!m_Psrc_y_count[resampler_range_check(m_cur_src_y, m_resample_src_y)])		{			m_cur_src_y++;			return true;		}		/* Find an empty slot in the scanline buffer. (FIXME: Perf. is terrible here with extreme scaling ratios.) */		for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)			if (m_Pscan_buf->scan_buf_y[i] == -1)				break;		/* If the buffer is full, exit with an error. */		if (i == MAX_SCAN_BUF_SIZE)		{			m_status = STATUS_SCAN_BUFFER_FULL;			return false;		}		m_Psrc_y_flag[resampler_range_check(m_cur_src_y, m_resample_src_y)] = true;		m_Pscan_buf->scan_buf_y[i] = m_cur_src_y;		/* Does this slot have any memory allocated to it? */		if (!m_Pscan_buf->scan_buf_l[i])		{			if ((m_Pscan_buf->scan_buf_l[i] = (Sample*)malloc(m_intermediate_x * sizeof(Sample))) == NULL)			{				m_status = STATUS_OUT_OF_MEMORY;				return false;			}		}		// Resampling on the X axis first?		if (m_delay_x_resample)		{			assert(m_intermediate_x == m_resample_src_x);			// Y-X resampling order			memcpy(m_Pscan_buf->scan_buf_l[i], Psrc, m_intermediate_x * sizeof(Sample));		}		else		{			assert(m_intermediate_x == m_resample_dst_x);			// X-Y resampling order			resample_x(m_Pscan_buf->scan_buf_l[i], Psrc);		}		m_cur_src_y++;		return true;	}	const Resampler::Sample* Resampler::get_line()	{		int i;		/* If all the destination lines have been		* generated, then always return NULL.		*/		if (m_cur_dst_y == m_resample_dst_y)			return NULL;		/* Check to see if all the required		* contributors are present, if not,		* return NULL.		*/		for (i = 0; i < m_Pclist_y[m_cur_dst_y].n; i++)			if (!m_Psrc_y_flag[resampler_range_check(m_Pclist_y[m_cur_dst_y].p[i].pixel, m_resample_src_y)])				return NULL;		resample_y(m_Pdst_buf);		m_cur_dst_y++;		return m_Pdst_buf;	}	Resampler::~Resampler()	{		int i;#if BASISU_RESAMPLER_DEBUG_OPS		printf("actual ops: %i\n", total_ops);#endif		free(m_Pdst_buf);		m_Pdst_buf = NULL;		if (m_Ptmp_buf)		{			free(m_Ptmp_buf);			m_Ptmp_buf = NULL;		}		/* Don't deallocate a contibutor list		* if the user passed us one of their own.	*/		if ((m_Pclist_x) && (!m_clist_x_forced))		{			free(m_Pclist_x->p);			free(m_Pclist_x);			m_Pclist_x = NULL;		}		if ((m_Pclist_y) && (!m_clist_y_forced))		{			free(m_Pclist_y->p);			free(m_Pclist_y);			m_Pclist_y = NULL;		}		free(m_Psrc_y_count);		m_Psrc_y_count = NULL;		free(m_Psrc_y_flag);		m_Psrc_y_flag = NULL;		if (m_Pscan_buf)		{			for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)				free(m_Pscan_buf->scan_buf_l[i]);			free(m_Pscan_buf);			m_Pscan_buf = NULL;		}	}	void Resampler::restart()	{		if (STATUS_OKAY != m_status)			return;		m_cur_src_y = m_cur_dst_y = 0;		int i, j;		for (i = 0; i < m_resample_src_y; i++)		{			m_Psrc_y_count[i] = 0;			m_Psrc_y_flag[i] = false;		}		for (i = 0; i < m_resample_dst_y; i++)		{			for (j = 0; j < m_Pclist_y[i].n; j++)				m_Psrc_y_count[resampler_range_check(m_Pclist_y[i].p[j].pixel, m_resample_src_y)]++;		}		for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)		{			m_Pscan_buf->scan_buf_y[i] = -1;			free(m_Pscan_buf->scan_buf_l[i]);			m_Pscan_buf->scan_buf_l[i] = NULL;		}	}	Resampler::Resampler(int src_x, int src_y,		int dst_x, int dst_y,		Boundary_Op boundary_op,		Resample_Real sample_low, Resample_Real sample_high,		const char* Pfilter_name,		Contrib_List * Pclist_x,		Contrib_List * Pclist_y,		Resample_Real filter_x_scale,		Resample_Real filter_y_scale,		Resample_Real src_x_ofs,		Resample_Real src_y_ofs)	{		int i, j;		Resample_Real support, (*func)(Resample_Real);		assert(src_x > 0);		assert(src_y > 0);		assert(dst_x > 0);		assert(dst_y > 0);#if BASISU_RESAMPLER_DEBUG_OPS		total_ops = 0;#endif		m_lo = sample_low;		m_hi = sample_high;		m_delay_x_resample = false;		m_intermediate_x = 0;		m_Pdst_buf = NULL;		m_Ptmp_buf = NULL;		m_clist_x_forced = false;		m_Pclist_x = NULL;		m_clist_y_forced = false;		m_Pclist_y = NULL;		m_Psrc_y_count = NULL;		m_Psrc_y_flag = NULL;		m_Pscan_buf = NULL;		m_status = STATUS_OKAY;		m_resample_src_x = src_x;		m_resample_src_y = src_y;		m_resample_dst_x = dst_x;		m_resample_dst_y = dst_y;		m_boundary_op = boundary_op;		if ((m_Pdst_buf = (Sample*)malloc(m_resample_dst_x * sizeof(Sample))) == NULL)		{			m_status = STATUS_OUT_OF_MEMORY;			return;		}		// Find the specified filter.		if (Pfilter_name == NULL)			Pfilter_name = BASISU_RESAMPLER_DEFAULT_FILTER;		for (i = 0; i < g_num_resample_filters; i++)			if (strcmp(Pfilter_name, g_resample_filters[i].name) == 0)				break;		if (i == g_num_resample_filters)		{			m_status = STATUS_BAD_FILTER_NAME;			return;		}		func = g_resample_filters[i].func;		support = g_resample_filters[i].support;		/* Create contributor lists, unless the user supplied custom lists. */		if (!Pclist_x)		{			m_Pclist_x = make_clist(m_resample_src_x, m_resample_dst_x, m_boundary_op, func, support, filter_x_scale, src_x_ofs);			if (!m_Pclist_x)			{				m_status = STATUS_OUT_OF_MEMORY;				return;			}		}		else		{			m_Pclist_x = Pclist_x;			m_clist_x_forced = true;		}		if (!Pclist_y)		{			m_Pclist_y = make_clist(m_resample_src_y, m_resample_dst_y, m_boundary_op, func, support, filter_y_scale, src_y_ofs);			if (!m_Pclist_y)			{				m_status = STATUS_OUT_OF_MEMORY;				return;			}		}		else		{			m_Pclist_y = Pclist_y;			m_clist_y_forced = true;		}		if ((m_Psrc_y_count = (int*)calloc(m_resample_src_y, sizeof(int))) == NULL)		{			m_status = STATUS_OUT_OF_MEMORY;			return;		}		if ((m_Psrc_y_flag = (unsigned char*)calloc(m_resample_src_y, sizeof(unsigned char))) == NULL)		{			m_status = STATUS_OUT_OF_MEMORY;			return;		}		// Count how many times each source line contributes to a destination line.		for (i = 0; i < m_resample_dst_y; i++)			for (j = 0; j < m_Pclist_y[i].n; j++)				m_Psrc_y_count[resampler_range_check(m_Pclist_y[i].p[j].pixel, m_resample_src_y)]++;		if ((m_Pscan_buf = (Scan_Buf*)malloc(sizeof(Scan_Buf))) == NULL)		{			m_status = STATUS_OUT_OF_MEMORY;			return;		}		for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)		{			m_Pscan_buf->scan_buf_y[i] = -1;			m_Pscan_buf->scan_buf_l[i] = NULL;		}		m_cur_src_y = m_cur_dst_y = 0;		{			// Determine which axis to resample first by comparing the number of multiplies required			// for each possibility.			int x_ops = count_ops(m_Pclist_x, m_resample_dst_x);			int y_ops = count_ops(m_Pclist_y, m_resample_dst_y);			// Hack 10/2000: Weight Y axis ops a little more than X axis ops.			// (Y axis ops use more cache resources.)			int xy_ops = x_ops * m_resample_src_y +				(4 * y_ops * m_resample_dst_x) / 3;			int yx_ops = (4 * y_ops * m_resample_src_x) / 3 +				x_ops * m_resample_dst_y;#if BASISU_RESAMPLER_DEBUG_OPS			printf("src: %i %i\n", m_resample_src_x, m_resample_src_y);			printf("dst: %i %i\n", m_resample_dst_x, m_resample_dst_y);			printf("x_ops: %i\n", x_ops);			printf("y_ops: %i\n", y_ops);			printf("xy_ops: %i\n", xy_ops);			printf("yx_ops: %i\n", yx_ops);#endif			// Now check which resample order is better. In case of a tie, choose the order			// which buffers the least amount of data.			if ((xy_ops > yx_ops) ||				((xy_ops == yx_ops) && (m_resample_src_x < m_resample_dst_x)))			{				m_delay_x_resample = true;				m_intermediate_x = m_resample_src_x;			}			else			{				m_delay_x_resample = false;				m_intermediate_x = m_resample_dst_x;			}#if BASISU_RESAMPLER_DEBUG_OPS			printf("delaying: %i\n", m_delay_x_resample);#endif		}		if (m_delay_x_resample)		{			if ((m_Ptmp_buf = (Sample*)malloc(m_intermediate_x * sizeof(Sample))) == NULL)			{				m_status = STATUS_OUT_OF_MEMORY;				return;			}		}	}	void Resampler::get_clists(Contrib_List * *ptr_clist_x, Contrib_List * *ptr_clist_y)	{		if (ptr_clist_x)			* ptr_clist_x = m_Pclist_x;		if (ptr_clist_y)			* ptr_clist_y = m_Pclist_y;	}	int Resampler::get_filter_num()	{		return g_num_resample_filters;	}	const char* Resampler::get_filter_name(int filter_num)	{		if ((filter_num < 0) || (filter_num >= g_num_resample_filters))			return NULL;		else			return g_resample_filters[filter_num].name;	}	} // namespace basisu
 |