Add software
This commit is contained in:
1088
Software/CubicSDR/src/util/DataTree.cpp
Normal file
1088
Software/CubicSDR/src/util/DataTree.cpp
Normal file
File diff suppressed because it is too large
Load Diff
637
Software/CubicSDR/src/util/DataTree.h
Normal file
637
Software/CubicSDR/src/util/DataTree.h
Normal file
@ -0,0 +1,637 @@
|
||||
#pragma once
|
||||
/*
|
||||
* DataElement/DataNode/DataTree -- structured serialization/deserialization system
|
||||
* designed for the CoolMule project :)
|
||||
*
|
||||
Copyright (C) 2003 by Charles J. Cliffe
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include <iostream>
|
||||
#include "tinyxml.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/* map comparison function */
|
||||
struct string_less
|
||||
{
|
||||
bool operator()(const std::string& a,const std::string& b) const
|
||||
{
|
||||
return a.compare(b) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* Data Exceptions */
|
||||
class DataException : public exception
|
||||
{
|
||||
private:
|
||||
string reason;
|
||||
|
||||
public:
|
||||
explicit DataException(const char *why) : reason(why) {}
|
||||
const char* what() const noexcept override { return reason.c_str(); }
|
||||
explicit operator string() { return reason; }
|
||||
};
|
||||
|
||||
|
||||
class DataTypeMismatchException : public DataException
|
||||
{
|
||||
public:
|
||||
explicit DataTypeMismatchException(const char *why) : DataException(why) { }
|
||||
};
|
||||
|
||||
|
||||
class DataInvalidChildException : public DataException
|
||||
{
|
||||
public:
|
||||
explicit DataInvalidChildException(const char *why) : DataException(why) { }
|
||||
};
|
||||
|
||||
|
||||
class DataElement
|
||||
{
|
||||
public :
|
||||
|
||||
enum class Type {
|
||||
DATA_NULL,
|
||||
DATA_CHAR,
|
||||
DATA_UCHAR,
|
||||
DATA_INT,
|
||||
DATA_UINT,
|
||||
DATA_LONG,
|
||||
DATA_ULONG,
|
||||
DATA_LONGLONG,
|
||||
DATA_FLOAT,
|
||||
DATA_DOUBLE,
|
||||
DATA_STRING,
|
||||
DATA_STR_VECTOR,
|
||||
DATA_CHAR_VECTOR,
|
||||
DATA_UCHAR_VECTOR,
|
||||
DATA_INT_VECTOR,
|
||||
DATA_UINT_VECTOR,
|
||||
DATA_LONG_VECTOR,
|
||||
DATA_ULONG_VECTOR,
|
||||
DATA_LONGLONG_VECTOR,
|
||||
DATA_FLOAT_VECTOR,
|
||||
DATA_DOUBLE_VECTOR,
|
||||
DATA_VOID,
|
||||
DATA_WSTRING
|
||||
};
|
||||
|
||||
typedef vector<unsigned char> DataElementBuffer;
|
||||
|
||||
typedef vector< DataElementBuffer > DataElementBufferVector;
|
||||
|
||||
private:
|
||||
Type data_type;
|
||||
|
||||
// raw buffer holding data_type element in bytes form.
|
||||
DataElementBuffer data_val;
|
||||
|
||||
//keep the vector of types in a spearate vector of DataElementBuffer.
|
||||
DataElementBufferVector data_val_vector;
|
||||
|
||||
//specializations to extract type: (need to be declared/done OUTSIDE of class scope else "Error: explicit specialization is not allowed in the current scope")
|
||||
//this is apparently fixed in C++17...
|
||||
// so we need to workaround it with a partial specialization using a fake Dummy parameter.
|
||||
|
||||
//if the exact right determineScalarDataType specialization was not used, throw exception at runtime.
|
||||
template<typename U, typename Dummy = int >
|
||||
Type determineScalarDataType(const U& /* type_in */) { throw DataTypeMismatchException("determineScalarDataType(U) usage with unsupported type !"); }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const char& /* type_in */) { return DataElement::Type::DATA_CHAR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const unsigned char& /* type_in */) { return DataElement::Type::DATA_UCHAR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const int& /* type_in */) { return DataElement::Type::DATA_INT; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const unsigned int& /* type_in */) { return DataElement::Type::DATA_UINT; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const long& /* type_in */) { return DataElement::Type::DATA_LONG; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const unsigned long& /* type_in */) { return DataElement::Type::DATA_ULONG; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const long long& /* type_in */) { return DataElement::Type::DATA_LONGLONG; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const float& /* type_in */) { return DataElement::Type::DATA_FLOAT; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineScalarDataType(const double& /* type_in */) { return DataElement::Type::DATA_DOUBLE; }
|
||||
|
||||
//vector versions:
|
||||
//if the exact right determineVectorDataType specialization was not used, throw exception at runtime.
|
||||
template<typename V, typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<V>& /* type_in */) { throw DataTypeMismatchException("determineVectorDataType(V) usage with unsupported type !"); }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<char>& /* type_in */) { return DataElement::Type::DATA_CHAR_VECTOR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<unsigned char>& /* type_in */) { return DataElement::Type::DATA_UCHAR_VECTOR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<int>& /* type_in */) { return DataElement::Type::DATA_INT_VECTOR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<unsigned int>& /* type_in */) { return DataElement::Type::DATA_UINT_VECTOR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<long>& /* type_in */) { return DataElement::Type::DATA_LONG_VECTOR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<unsigned long>& /* type_in */) { return DataElement::Type::DATA_ULONG_VECTOR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<long long>& /* type_in */) { return DataElement::Type::DATA_LONGLONG_VECTOR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<float>& /* type_in */) { return DataElement::Type::DATA_FLOAT_VECTOR; }
|
||||
|
||||
template< typename Dummy = int >
|
||||
Type determineVectorDataType(const vector<double>& /* type_in */) { return DataElement::Type::DATA_DOUBLE_VECTOR; }
|
||||
|
||||
public:
|
||||
|
||||
DataElement();
|
||||
DataElement(DataElement &cloneFrom);
|
||||
~DataElement();
|
||||
|
||||
Type getDataType();
|
||||
char *getDataPointer();
|
||||
size_t getDataSize();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//set overloads :
|
||||
|
||||
// general templates : for scalars
|
||||
template<typename T, typename Dummy = int >
|
||||
void set(const T& scalar_in) {
|
||||
|
||||
data_type = determineScalarDataType<T>(scalar_in);
|
||||
|
||||
int unit_size = sizeof(T);
|
||||
//copy in a temporary variable (needed ?)
|
||||
T local_copy = scalar_in;
|
||||
auto* local_copy_ptr = reinterpret_cast<unsigned char*>(&local_copy);
|
||||
|
||||
data_val.assign(local_copy_ptr, local_copy_ptr + unit_size);
|
||||
}
|
||||
|
||||
// general templates : for vector of scalars
|
||||
template<typename T, typename Dummy = int >
|
||||
void set(const vector<T>& scalar_vector_in) {
|
||||
|
||||
data_type = determineVectorDataType<T>(scalar_vector_in);
|
||||
|
||||
int unit_size = sizeof(T);
|
||||
|
||||
data_val_vector.clear();
|
||||
|
||||
DataElementBuffer single_buffer;
|
||||
|
||||
for (auto single_element : scalar_vector_in) {
|
||||
|
||||
//copy in a temporary variable (needed ?)
|
||||
T local_copy = single_element;
|
||||
auto* local_copy_ptr = reinterpret_cast<unsigned char*>(&local_copy);
|
||||
|
||||
single_buffer.assign(local_copy_ptr, local_copy_ptr + unit_size);
|
||||
|
||||
data_val_vector.push_back(single_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
//template specialization : for string
|
||||
template< typename Dummy = int >
|
||||
void set(const string& str_in) {
|
||||
|
||||
data_type = DataElement::Type::DATA_STRING;
|
||||
|
||||
data_val.assign(str_in.begin(), str_in.end());
|
||||
}
|
||||
|
||||
//template specialization : for wstring
|
||||
template< typename Dummy = int >
|
||||
void set(const wstring& wstr_in) {
|
||||
|
||||
data_type = DataElement::Type::DATA_WSTRING;
|
||||
|
||||
//wchar_t is tricky, the terminating zero is actually a (wchar_t)0 !
|
||||
//wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
|
||||
size_t maxLenBytes = (wstr_in.length() + 1) * sizeof(wchar_t);
|
||||
|
||||
//be paranoid, zero the buffer
|
||||
char *tmp_str = (char *)::calloc(maxLenBytes, sizeof(char));
|
||||
|
||||
//if something awful happens, the last sizeof(wchar_t) is at least zero...
|
||||
::wcstombs(tmp_str, wstr_in.c_str(), maxLenBytes - sizeof(wchar_t));
|
||||
|
||||
data_val.assign(tmp_str, tmp_str + maxLenBytes - sizeof(wchar_t));
|
||||
|
||||
::free(tmp_str);
|
||||
}
|
||||
|
||||
//template specialization : for vector<string>
|
||||
template< typename Dummy = int >
|
||||
void set(const vector<string>& vector_str_in) {
|
||||
|
||||
data_type = DataElement::Type::DATA_STR_VECTOR;
|
||||
|
||||
data_val_vector.clear();
|
||||
|
||||
DataElementBuffer single_buffer;
|
||||
|
||||
for (auto single_element : vector_str_in) {
|
||||
|
||||
single_buffer.assign(single_element.begin(), single_element.end());
|
||||
|
||||
data_val_vector.push_back(single_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///specific versions
|
||||
void set(const std::set<string> &strset_in);
|
||||
void set(const char *data_in, long size_in); /* voids, file chunks anyone? */
|
||||
void set(const char *data_in); /* strings, stops at NULL, returns as string */
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/* get overloads */
|
||||
|
||||
template<typename T, typename Dummy = int >
|
||||
void get(T& scalar_out) {
|
||||
|
||||
if (getDataSize() == 0) {
|
||||
throw DataException("Cannot get() the scalar, DataElement is empty !");
|
||||
}
|
||||
|
||||
DataElement::Type storageType = getDataType();
|
||||
|
||||
//TODO: smarter way with templates ?
|
||||
if (storageType == DataElement::Type::DATA_CHAR) {
|
||||
char* storage_ptr = reinterpret_cast<char*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
|
||||
} else if (storageType == DataElement::Type::DATA_UCHAR) {
|
||||
auto* storage_ptr = reinterpret_cast<unsigned char*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_INT) {
|
||||
int* storage_ptr = reinterpret_cast<int*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_UINT) {
|
||||
auto* storage_ptr = reinterpret_cast<unsigned int*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_LONG) {
|
||||
long* storage_ptr = reinterpret_cast<long*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_ULONG) {
|
||||
auto* storage_ptr = reinterpret_cast<unsigned long*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_LONGLONG) {
|
||||
auto* storage_ptr = reinterpret_cast<long long*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_FLOAT) {
|
||||
auto* storage_ptr = reinterpret_cast<float*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_DOUBLE) {
|
||||
auto* storage_ptr = reinterpret_cast<double*>(&data_val[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// general templates : for vector of scalars
|
||||
template<typename T, typename Dummy = int >
|
||||
void get(vector<T>& scalar_vector_out) {
|
||||
|
||||
scalar_vector_out.clear();
|
||||
|
||||
DataElementBuffer single_buffer;
|
||||
|
||||
Type storageType = getDataType();
|
||||
|
||||
for (auto single_storage_element : data_val_vector) {
|
||||
|
||||
if (single_storage_element.empty()) {
|
||||
throw DataException("Cannot get(vector<scalar>) on single element because it is empty!");
|
||||
}
|
||||
|
||||
T scalar_out;
|
||||
|
||||
//TODO: smarter way with templates ?
|
||||
if (storageType == DataElement::Type::DATA_CHAR_VECTOR) {
|
||||
char* storage_ptr = reinterpret_cast<char*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
|
||||
} else if (storageType == DataElement::Type::DATA_UCHAR_VECTOR) {
|
||||
auto* storage_ptr = reinterpret_cast<unsigned char*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_INT_VECTOR) {
|
||||
int* storage_ptr = reinterpret_cast<int*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_UINT_VECTOR) {
|
||||
auto* storage_ptr = reinterpret_cast<unsigned int*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_LONG_VECTOR) {
|
||||
long* storage_ptr = reinterpret_cast<long*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_ULONG_VECTOR) {
|
||||
auto* storage_ptr = reinterpret_cast<unsigned long*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_LONGLONG_VECTOR) {
|
||||
auto* storage_ptr = reinterpret_cast<long long*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_FLOAT_VECTOR) {
|
||||
auto* storage_ptr = reinterpret_cast<float*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
} else if (storageType == DataElement::Type::DATA_DOUBLE_VECTOR) {
|
||||
auto* storage_ptr = reinterpret_cast<double*>(&single_storage_element[0]);
|
||||
//constructor-like
|
||||
scalar_out = T(*storage_ptr);
|
||||
}
|
||||
|
||||
scalar_vector_out.push_back(scalar_out);
|
||||
} //end for.
|
||||
}
|
||||
|
||||
//template specialization : for string or void* returned as string
|
||||
template< typename Dummy = int >
|
||||
void get(string& str_out) {
|
||||
|
||||
//reset
|
||||
str_out.clear();
|
||||
|
||||
if (data_type == DataElement::Type::DATA_NULL) {
|
||||
//it means TinyXML has parsed an empty tag,
|
||||
//so return an empty string.
|
||||
return;
|
||||
}
|
||||
|
||||
if (data_type != DataElement::Type::DATA_STRING && data_type != DataElement::Type::DATA_VOID) {
|
||||
throw(DataTypeMismatchException("Type mismatch, neither a STRING nor a VOID*"));
|
||||
}
|
||||
|
||||
for (auto single_char : data_val) {
|
||||
str_out.push_back((char)single_char);
|
||||
}
|
||||
}
|
||||
|
||||
//template specialization : for wstring
|
||||
template< typename Dummy = int >
|
||||
void get(wstring& wstr_out) {
|
||||
|
||||
//reset
|
||||
wstr_out.clear();
|
||||
|
||||
if (data_type == DataElement::Type::DATA_NULL) {
|
||||
//it means TinyXML has parsed an empty tag,
|
||||
//so return an empty string.
|
||||
return;
|
||||
}
|
||||
|
||||
if (data_type != DataElement::Type::DATA_WSTRING) {
|
||||
throw(DataTypeMismatchException("Type mismatch, not a WSTRING"));
|
||||
}
|
||||
|
||||
if (getDataSize() >= sizeof(wchar_t)) {
|
||||
|
||||
//data_val is an array of bytes holding wchar_t characters, plus a terminating (wchar_t)0
|
||||
//wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
|
||||
size_t maxNbWchars = getDataSize() / sizeof(wchar_t);
|
||||
|
||||
//be paranoid, zero the buffer
|
||||
auto *tmp_wstr = (wchar_t *)::calloc(maxNbWchars + 1, sizeof(wchar_t));
|
||||
|
||||
//the last wchar_t is actually zero if anything goes wrong...
|
||||
::mbstowcs(tmp_wstr, (const char*)&data_val[0], maxNbWchars);
|
||||
|
||||
wstr_out.assign(tmp_wstr);
|
||||
|
||||
::free(tmp_wstr);
|
||||
}
|
||||
}
|
||||
|
||||
//template specialization : for vector<string>
|
||||
template< typename Dummy = int >
|
||||
void get(vector<string>& vector_str_out) {
|
||||
|
||||
if (data_type != DataElement::Type::DATA_STR_VECTOR) {
|
||||
throw(DataTypeMismatchException("Type mismatch, not a STRING VECTOR"));
|
||||
}
|
||||
|
||||
vector_str_out.clear();
|
||||
|
||||
string single_buffer;
|
||||
|
||||
for (auto single_element : data_val_vector) {
|
||||
|
||||
single_buffer.assign(single_element.begin(), single_element.end());
|
||||
|
||||
vector_str_out.push_back(single_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
//special versions:
|
||||
void get(DataElementBuffer& data_out); /* getting a void or string */
|
||||
void get(std::set<string> &strset_out);
|
||||
|
||||
|
||||
/* special get functions, saves creating unnecessary vars */
|
||||
int getChar() { char i_get; get(i_get); return i_get; };
|
||||
unsigned int getUChar() { unsigned char i_get; get(i_get); return i_get; };
|
||||
int getInt() { int i_get; get(i_get); return i_get; };
|
||||
unsigned int getUInt() { unsigned int i_get; get(i_get); return i_get; };
|
||||
long getLong() { long l_get; get(l_get); return l_get; };
|
||||
unsigned long getULong() { unsigned long l_get; get(l_get); return l_get; };
|
||||
long long getLongLong() { long long l_get; get(l_get); return l_get; };
|
||||
float getFloat() { float f_get; get(f_get); return f_get; };
|
||||
double getDouble() { double d_get; get(d_get); return d_get; };
|
||||
|
||||
std::string toString();
|
||||
};
|
||||
|
||||
///
|
||||
class DataNode
|
||||
{
|
||||
private:
|
||||
DataNode *parentNode;
|
||||
vector<DataNode *> children;
|
||||
map<string, vector<DataNode *>, string_less> childmap;
|
||||
map<string, unsigned int, string_less> childmap_ptr;
|
||||
|
||||
string node_name;
|
||||
DataElement *data_elem;
|
||||
unsigned int ptr;
|
||||
|
||||
|
||||
public:
|
||||
DataNode();
|
||||
explicit DataNode(const char *name_in);
|
||||
DataNode(const char *name_in, DataElement &cloneFrom);
|
||||
DataNode(const char *name_in, DataNode &cloneFrom);
|
||||
|
||||
~DataNode();
|
||||
|
||||
void setName(const char *name_in);
|
||||
string &getName() { return node_name; }
|
||||
|
||||
DataNode *getParentNode() { return parentNode; };
|
||||
void setParentNode(DataNode &parentNode_in) { parentNode = &parentNode_in; };
|
||||
|
||||
size_t numChildren(); /* Number of children */
|
||||
size_t numChildren(const char *name_in); /* Number of children named 'name_in' */
|
||||
|
||||
DataElement *element(); /* DataElement at this node */
|
||||
|
||||
DataNode *newChild(const char *name_in);
|
||||
DataNode *newChild(const char *name_in, DataNode *otherNode);
|
||||
DataNode *newChildCloneFrom(const char *name_in, DataNode *cloneFrom);
|
||||
DataNode *child(const char *name_in, int index = 0);
|
||||
DataNode *child(int index);
|
||||
|
||||
|
||||
bool hasAnother(const char *name_in); /* useful for while() loops in conjunction with getNext() */
|
||||
bool hasAnother();
|
||||
DataNode *getNext(const char *name_in); /* get next of specified name */
|
||||
DataNode *getNext(); /* get next child */
|
||||
void rewind(const char *name_in); /* rewind specific */
|
||||
void rewind(); /* rewind generic */
|
||||
void rewindAll();
|
||||
|
||||
void findAll(const char *name_in, vector<DataNode *> &node_list_out);
|
||||
|
||||
explicit operator string () { string s; element()->get(s); return s; }
|
||||
explicit operator const char * () { if (element()->getDataType() == DataElement::Type::DATA_STRING) { return element()->getDataPointer(); } else { return nullptr; } }
|
||||
explicit operator char () { char v=0; element()->get(v); return v; }
|
||||
explicit operator unsigned char () { unsigned char v=0; element()->get(v); return v; }
|
||||
explicit operator int () { int v=0; element()->get(v); return v; }
|
||||
explicit operator unsigned int () { unsigned int v=0; element()->get(v); return v; }
|
||||
explicit operator long () { long v=0; element()->get(v); return v; }
|
||||
explicit operator unsigned long () { unsigned long v=0; element()->get(v); return v; }
|
||||
explicit operator long long () { long long v=0; element()->get(v); return v; }
|
||||
explicit operator float () { float v=0; element()->get(v); return v; }
|
||||
explicit operator double () { double v=0; element()->get(v); return v; }
|
||||
|
||||
explicit operator vector<char> () { vector<char> v; element()->get(v); return v; }
|
||||
explicit operator vector<unsigned char> () { vector<unsigned char> v; element()->get(v); return v; }
|
||||
explicit operator vector<int> () { vector<int> v; element()->get(v); return v; }
|
||||
explicit operator vector<unsigned int> () { vector<unsigned int> v; element()->get(v); return v; }
|
||||
explicit operator vector<long> () { vector<long> v; element()->get(v); return v; }
|
||||
explicit operator vector<unsigned long> () { vector<unsigned long> v; element()->get(v); return v; }
|
||||
explicit operator vector<float> () { vector<float> v; element()->get(v); return v; }
|
||||
explicit operator vector<double> () { vector<double> v; element()->get(v); return v; }
|
||||
|
||||
const string &operator= (const string &s) { element()->set(s); return s; }
|
||||
const wstring &operator= (const wstring &s) { element()->set(s); return s; }
|
||||
|
||||
char operator= (char i) { element()->set(i); return i; }
|
||||
unsigned char operator= (unsigned char i) { element()->set(i); return i; }
|
||||
int operator= (int i) { element()->set(i); return i; }
|
||||
unsigned int operator= (unsigned int i) { element()->set(i); return i; }
|
||||
long operator= (long i) { element()->set(i); return i; }
|
||||
unsigned long operator= (unsigned long i) { element()->set(i); return i; }
|
||||
long long operator= (long long i) { element()->set(i); return i; }
|
||||
float operator= (float i) { element()->set(i); return i; }
|
||||
double operator= (double i) { element()->set(i); return i; }
|
||||
|
||||
vector<char> &operator= (vector<char> &v) { element()->set(v); return v; }
|
||||
vector<unsigned char> &operator= (vector<unsigned char> &v) { element()->set(v); return v; }
|
||||
vector<int> &operator= (vector<int> &v) { element()->set(v); return v; }
|
||||
vector<unsigned int> &operator= (vector<unsigned int> &v) { element()->set(v); return v; }
|
||||
vector<long> &operator= (vector<long> &v) { element()->set(v); return v; }
|
||||
vector<unsigned long> &operator= (vector<unsigned long> &v) { element()->set(v); return v; }
|
||||
vector<float> &operator= (vector<float> &v) { element()->set(v); return v; }
|
||||
vector<double> &operator= (vector<double> &v) { element()->set(v); return v; }
|
||||
|
||||
DataNode *operator[] (const char *name_in) { return getNext(name_in); }
|
||||
DataNode *operator[] (int idx) { return child(idx); }
|
||||
|
||||
bool operator() (const char *name_in) { return hasAnother(name_in); }
|
||||
bool operator() () { return hasAnother(); }
|
||||
|
||||
DataNode *operator ^(const char *name_in) { return newChild(name_in); }
|
||||
};
|
||||
|
||||
|
||||
typedef vector<DataNode *> DataNodeList;
|
||||
|
||||
enum DT_FloatingPointPolicy {
|
||||
USE_FLOAT,
|
||||
USE_DOUBLE
|
||||
};
|
||||
|
||||
class DataTree
|
||||
{
|
||||
private:
|
||||
DataNode dn_root;
|
||||
|
||||
string wsEncode(const wstring& wstr);
|
||||
wstring wsDecode(const string& str);
|
||||
|
||||
public:
|
||||
explicit DataTree(const char *name_in);
|
||||
DataTree();
|
||||
~DataTree();
|
||||
|
||||
DataNode *rootNode();
|
||||
|
||||
void nodeToXML(DataNode *elem, TiXmlElement *elxml);
|
||||
void setFromXML(DataNode *elem, TiXmlNode *elxml, bool root_node=true, DT_FloatingPointPolicy fpp=USE_FLOAT);
|
||||
void decodeXMLText(DataNode *elem, const char *in_text, DT_FloatingPointPolicy fpp);
|
||||
|
||||
void printXML(); /* print datatree as XML */
|
||||
|
||||
bool LoadFromFileXML(const std::string& filename, DT_FloatingPointPolicy fpp=USE_FLOAT);
|
||||
bool SaveToFileXML(const std::string& filename);
|
||||
|
||||
};
|
104
Software/CubicSDR/src/util/GLExt.cpp
Normal file
104
Software/CubicSDR/src/util/GLExt.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include "GLExt.h"
|
||||
#include <iostream>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#include <GL/glx.h>
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
PFNWGLGETEXTENSIONSSTRINGEXTPROC wglGetExtensionsStringEXT = NULL;
|
||||
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
|
||||
PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT = NULL;
|
||||
|
||||
bool GLExtSupported(const char *extension_name) {
|
||||
const GLubyte *extensions = glGetString(GL_EXTENSIONS);
|
||||
|
||||
return (std::strstr((const char *)extensions, extension_name) != NULL);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
bool GLExt_initialized = false;
|
||||
|
||||
void initGLExtensions() {
|
||||
if (GLExt_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// const GLubyte *extensions = glGetString(GL_EXTENSIONS);
|
||||
// std::cout << std::endl << "Supported GL Extensions: " << std::endl << extensions << std::endl << std::endl;
|
||||
|
||||
#ifdef __APPLE__
|
||||
const GLint interval = 1;
|
||||
#else
|
||||
const GLint interval = 2;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (GLExtSupported("WGL_EXT_swap_control")) {
|
||||
std::cout << "Initializing WGL swap control extensions.." << std::endl << std::flush;
|
||||
|
||||
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT");
|
||||
wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC) wglGetProcAddress("wglGetSwapIntervalEXT");
|
||||
|
||||
wglSwapIntervalEXT(interval);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// OSX is just ON / OFF
|
||||
CGLSetParameter (CGLGetCurrentContext(), kCGLCPSwapInterval, &interval);
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
dlopen("libglx.so",RTLD_LAZY);
|
||||
|
||||
void (*glxSwapIntervalEXTFunc) (Display *dpy, GLXDrawable drawable, int interval) = nullptr;
|
||||
int (*glxSwapIntervalMESAFunc)(unsigned int interval) = nullptr;
|
||||
int (*glxSwapIntervalSGIFunc) (int interval) = nullptr;
|
||||
void (*DRI2SwapIntervalFunc) (Display *dpy, XID drawable, int interval) = nullptr;
|
||||
|
||||
glxSwapIntervalEXTFunc = (void (*) (Display *dpy, GLXDrawable drawable, int interval)) dlsym(RTLD_DEFAULT,"glXSwapIntervalEXT");
|
||||
glxSwapIntervalMESAFunc = (int (*)(unsigned int interval)) dlsym(RTLD_DEFAULT,"glXSwapIntervalMESA");
|
||||
glxSwapIntervalSGIFunc = (int (*) (int interval)) dlsym(RTLD_DEFAULT,"glXSwapIntervalSGI");
|
||||
DRI2SwapIntervalFunc = (void (*) (Display *dpy, XID drawable, int interval)) dlsym(RTLD_DEFAULT,"DRI2SwapInterval");
|
||||
|
||||
std::cout << "Available vertical sync SwapInterval functions: " << std::endl;
|
||||
std::cout << "\tglxSwapIntervalEXT: " << ((glxSwapIntervalEXTFunc != nullptr)?"Yes":"No") << std::endl;
|
||||
std::cout << "\tDRI2SwapInterval: " << ((DRI2SwapIntervalFunc != nullptr)?"Yes":"No") << std::endl;
|
||||
std::cout << "\tglxSwapIntervalMESA: " << ((glxSwapIntervalMESAFunc != nullptr)?"Yes":"No") << std::endl;
|
||||
std::cout << "\tglxSwapIntervalSGI: " << ((glxSwapIntervalSGIFunc != nullptr)?"Yes":"No") << std::endl;
|
||||
|
||||
if (glxSwapIntervalEXTFunc) {
|
||||
Display *dpy = glXGetCurrentDisplay();
|
||||
GLXDrawable drawable = glXGetCurrentDrawable();
|
||||
glxSwapIntervalEXTFunc(dpy, drawable, interval);
|
||||
std::cout << "Using glxSwapIntervalEXT." << std::endl << std::endl;
|
||||
} else if (DRI2SwapIntervalFunc) {
|
||||
Display *dpy = glXGetCurrentDisplay();
|
||||
GLXDrawable drawable = glXGetCurrentDrawable();
|
||||
DRI2SwapIntervalFunc(dpy, drawable, interval);
|
||||
std::cout << "Using DRI2SwapInterval." << std::endl << std::endl;
|
||||
} else if (glxSwapIntervalMESAFunc) {
|
||||
glxSwapIntervalMESAFunc(interval);
|
||||
std::cout << "Using glxSwapIntervalMESA." << std::endl << std::endl;
|
||||
} else if (glxSwapIntervalSGIFunc) {
|
||||
glxSwapIntervalSGIFunc(interval);
|
||||
std::cout << "Using glxSwapIntervalSGI." << std::endl << std::endl;
|
||||
} else {
|
||||
std::cout << "No vertical sync swap interval functions available." << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
GLExt_initialized = true;
|
||||
}
|
27
Software/CubicSDR/src/util/GLExt.h
Normal file
27
Software/CubicSDR/src/util/GLExt.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wx/glcanvas.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#ifdef __MINGW32__
|
||||
#include <gl/wglext.h>
|
||||
#else
|
||||
#include "wglext.h"
|
||||
#endif
|
||||
|
||||
extern PFNWGLGETEXTENSIONSSTRINGEXTPROC _wglGetExtensionsStringEXT;
|
||||
extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;
|
||||
extern PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT;
|
||||
|
||||
bool GLExtSupported(const char *extension_name);
|
||||
|
||||
#endif
|
||||
|
||||
extern bool GLExt_initialized;
|
||||
|
||||
void initGLExtensions();
|
||||
|
920
Software/CubicSDR/src/util/GLFont.cpp
Normal file
920
Software/CubicSDR/src/util/GLFont.cpp
Normal file
@ -0,0 +1,920 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include "GLFont.h"
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include "cubic_math.h"
|
||||
|
||||
#ifdef _OSX_APP_
|
||||
#include "CoreFoundation/CoreFoundation.h"
|
||||
#endif
|
||||
|
||||
#ifndef RES_FOLDER
|
||||
#define RES_FOLDER L""
|
||||
#endif
|
||||
|
||||
#define GC_DRAW_COUNT_PERIOD 50
|
||||
#define GC_DRAW_COUNT_LIMIT 10
|
||||
|
||||
GLFontStringCache::GLFontStringCache() {
|
||||
gc = 0;
|
||||
}
|
||||
|
||||
//Static initialization of all available fonts,
|
||||
//using aggregate syntax (Cx11+)
|
||||
|
||||
//Fonts must be listed in increasing size for Drawer to work !
|
||||
GLFont GLFont::fonts[GLFont::GLFontSize::GLFONT_SIZE_MAX] = {
|
||||
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE12, L"fonts/vera_sans_mono12.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE16, L"fonts/vera_sans_mono16.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE18, L"fonts/vera_sans_mono18.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE24, L"fonts/vera_sans_mono24.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE27, L"fonts/vera_sans_mono27.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE32, L"fonts/vera_sans_mono32.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE36, L"fonts/vera_sans_mono36.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE48, L"fonts/vera_sans_mono48.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE64, L"fonts/vera_sans_mono64.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE72, L"fonts/vera_sans_mono72.fnt" },
|
||||
{ GLFont::GLFontSize::GLFONT_SIZE96, L"fonts/vera_sans_mono96.fnt" },
|
||||
|
||||
};
|
||||
|
||||
|
||||
std::atomic<GLFont::GLFontScale> GLFont::currentScale{ GLFont::GLFontScale::GLFONT_SCALE_NORMAL };
|
||||
|
||||
|
||||
GLFontChar::GLFontChar() :
|
||||
id(0), x(0), y(0), width(0), height(0), xoffset(0), yoffset(0), xadvance(0), aspect(1), index(0) {
|
||||
|
||||
}
|
||||
|
||||
GLFontChar::~GLFontChar() = default;
|
||||
|
||||
void GLFontChar::setId(int idval) {
|
||||
id = idval;
|
||||
}
|
||||
|
||||
int GLFontChar::getId() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
void GLFontChar::setXOffset(int xofs) {
|
||||
xoffset = xofs;
|
||||
}
|
||||
|
||||
int GLFontChar::getXOffset() {
|
||||
return xoffset;
|
||||
}
|
||||
|
||||
void GLFontChar::setYOffset(int yofs) {
|
||||
yoffset = yofs;
|
||||
}
|
||||
|
||||
int GLFontChar::getYOffset() {
|
||||
return yoffset;
|
||||
}
|
||||
|
||||
void GLFontChar::setX(int xpos) {
|
||||
x = xpos;
|
||||
}
|
||||
|
||||
int GLFontChar::getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
void GLFontChar::setY(int ypos) {
|
||||
y = ypos;
|
||||
}
|
||||
|
||||
int GLFontChar::getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
void GLFontChar::setWidth(int w) {
|
||||
width = w;
|
||||
if (width && height) {
|
||||
aspect = (float) width / (float) height;
|
||||
}
|
||||
}
|
||||
|
||||
int GLFontChar::getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
void GLFontChar::setHeight(int h) {
|
||||
height = h;
|
||||
if (width && height) {
|
||||
aspect = (float) width / (float) height;
|
||||
}
|
||||
}
|
||||
|
||||
int GLFontChar::getHeight() const {
|
||||
return height;
|
||||
}
|
||||
|
||||
void GLFontChar::setXAdvance(int xadv) {
|
||||
xadvance = xadv;
|
||||
}
|
||||
|
||||
int GLFontChar::getXAdvance() {
|
||||
return xadvance;
|
||||
}
|
||||
|
||||
float GLFontChar::getAspect() const {
|
||||
return aspect;
|
||||
}
|
||||
|
||||
void GLFontChar::setIndex(unsigned int idx) {
|
||||
index = idx;
|
||||
}
|
||||
|
||||
int GLFontChar::getIndex() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
GLFont::GLFont(GLFontSize size, std::wstring defFileName):
|
||||
lineHeight(0), base(0), imageWidth(0), imageHeight(0), loaded(false), texId(0), gcCounter(0) {
|
||||
|
||||
fontSizeClass = size;
|
||||
|
||||
fontDefFileSource = defFileName;
|
||||
}
|
||||
|
||||
GLFont::~GLFont() = default;
|
||||
|
||||
std::wstring GLFont::nextParam(std::wistringstream &str) {
|
||||
std::wstring param_str;
|
||||
|
||||
str >> param_str;
|
||||
|
||||
if (param_str.find(L'"') != std::wstring::npos) {
|
||||
std::wstring rest;
|
||||
while (!str.eof() && (std::count(param_str.begin(), param_str.end(), L'"') % 2)) {
|
||||
str >> rest;
|
||||
param_str.append(L" " + rest);
|
||||
}
|
||||
}
|
||||
|
||||
return param_str;
|
||||
}
|
||||
|
||||
std::wstring GLFont::getParamKey(const std::wstring& param_str) {
|
||||
std::wstring keyName;
|
||||
|
||||
size_t eqpos = param_str.find(L'=');
|
||||
|
||||
if (eqpos != std::wstring::npos) {
|
||||
keyName = param_str.substr(0, eqpos);
|
||||
}
|
||||
|
||||
return keyName;
|
||||
}
|
||||
|
||||
std::wstring GLFont::getParamValue(const std::wstring& param_str) {
|
||||
std::wstring value;
|
||||
|
||||
size_t eqpos = param_str.find(L'=');
|
||||
|
||||
if (eqpos != std::wstring::npos) {
|
||||
value = param_str.substr(eqpos + 1);
|
||||
}
|
||||
|
||||
if (value[0] == L'"' && value[value.length() - 1] == L'"') {
|
||||
value = value.substr(1, value.length() - 2);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void GLFont::loadFontOnce() {
|
||||
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if _OSX_APP_
|
||||
CFBundleRef mainBundle = CFBundleGetMainBundle();
|
||||
CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
|
||||
char path[PATH_MAX];
|
||||
if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX))
|
||||
{
|
||||
// error!
|
||||
}
|
||||
CFRelease(resourcesURL);
|
||||
wxString resourceFolder = std::string(path) + "/";
|
||||
|
||||
#else
|
||||
wxString resourceFolder = RES_FOLDER;
|
||||
#endif
|
||||
|
||||
wxFileName exePath = wxFileName(wxStandardPaths::Get().GetExecutablePath());
|
||||
|
||||
//1) First try : RES_FOLDER/fonts/*
|
||||
wxFileName fontDefFileName = wxFileName(resourceFolder + L"/" + fontDefFileSource);
|
||||
|
||||
bool fontFilePathFound = fontDefFileName.Exists();
|
||||
|
||||
// 2) Second try: [Cubic exe path]/RES_FOLDER/fonts/*
|
||||
if (!fontFilePathFound) {
|
||||
|
||||
fontDefFileName = wxFileName(exePath.GetPath() + L"/" + RES_FOLDER + L"/" + fontDefFileSource);
|
||||
fontFilePathFound = fontDefFileName.Exists();
|
||||
}
|
||||
|
||||
// 3) Third try: [Cubic exe path]/fonts/*
|
||||
if (!fontFilePathFound) {
|
||||
|
||||
fontDefFileName = wxFileName(exePath.GetPath() + L"/" + fontDefFileSource);
|
||||
fontFilePathFound = fontDefFileName.Exists();
|
||||
}
|
||||
|
||||
if (fontFilePathFound) {
|
||||
|
||||
if (!fontDefFileName.IsFileReadable()) {
|
||||
std::cout << "Font file " << fontDefFileName.GetFullPath() << " is not readable?" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (!fontDefFileName.FileExists()) {
|
||||
std::cout << "Font file " << fontDefFileName.GetFullPath() << " does not exist?" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Re-compute the resource dir.
|
||||
resourceFolder = fontDefFileName.GetPath();
|
||||
|
||||
std::string fontDefFileNamePath = fontDefFileName.GetFullPath(wxPATH_NATIVE).ToStdString();
|
||||
|
||||
std::wifstream input;
|
||||
|
||||
input.open(fontDefFileNamePath, std::ios::in);
|
||||
|
||||
std::wstring op;
|
||||
|
||||
while (!input.eof()) {
|
||||
input >> op;
|
||||
if (op == L"info") {
|
||||
std::wstring info_param_str;
|
||||
getline(input, info_param_str);
|
||||
std::wistringstream info_param(info_param_str);
|
||||
|
||||
while (!info_param.eof()) {
|
||||
std::wstring param = nextParam(info_param);
|
||||
|
||||
std::wstring paramKey = getParamKey(param);
|
||||
if (paramKey == L"face") {
|
||||
fontName = getParamValue(param);
|
||||
}
|
||||
|
||||
param = nextParam(info_param);
|
||||
paramKey = getParamKey(param);
|
||||
if (paramKey == L"size") {
|
||||
|
||||
std::wistringstream paramValue(getParamValue(param));
|
||||
paramValue >> pixHeight;
|
||||
}
|
||||
|
||||
// std::cout << "[" << paramKey << "] = '" << paramValue << "'" << std::endl;
|
||||
}
|
||||
} else if (op == L"common") {
|
||||
std::wstring common_param_str;
|
||||
getline(input, common_param_str);
|
||||
std::wistringstream common_param(common_param_str);
|
||||
|
||||
while (!common_param.eof()) {
|
||||
std::wstring param = nextParam(common_param);
|
||||
|
||||
std::wstring paramKey = getParamKey(param);
|
||||
std::wistringstream paramValue(getParamValue(param));
|
||||
|
||||
if (paramKey == L"lineHeight") {
|
||||
paramValue >> lineHeight;
|
||||
} else if (paramKey == L"base") {
|
||||
paramValue >> base;
|
||||
} else if (paramKey == L"scaleW") {
|
||||
paramValue >> imageWidth;
|
||||
} else if (paramKey == L"scaleH") {
|
||||
paramValue >> imageHeight;
|
||||
}
|
||||
// std::cout << "[" << paramKey << "] = '" << getParamValue(param) << "'" << std::endl;
|
||||
}
|
||||
} else if (op == L"page") {
|
||||
std::wstring page_param_str;
|
||||
getline(input, page_param_str);
|
||||
std::wistringstream page_param(page_param_str);
|
||||
|
||||
while (!page_param.eof()) {
|
||||
std::wstring param = nextParam(page_param);
|
||||
|
||||
std::wstring paramKey = getParamKey(param);
|
||||
std::wstring paramValue = getParamValue(param);
|
||||
|
||||
if (paramKey == L"file") {
|
||||
wxFileName imgFileName = wxFileName(resourceFolder, paramValue);
|
||||
imageFile = imgFileName.GetFullPath(wxPATH_NATIVE).ToStdWstring();
|
||||
}
|
||||
// std::cout << "[" << paramKey << "] = '" << paramValue << "'" << std::endl;
|
||||
}
|
||||
|
||||
} else if (op == L"char") {
|
||||
std::wstring char_param_str;
|
||||
getline(input, char_param_str);
|
||||
std::wistringstream char_param(char_param_str);
|
||||
|
||||
auto *newChar = new GLFontChar;
|
||||
|
||||
while (!char_param.eof()) {
|
||||
std::wstring param = nextParam(char_param);
|
||||
|
||||
std::wstring paramKey = getParamKey(param);
|
||||
std::wistringstream paramValue(getParamValue(param));
|
||||
|
||||
int val;
|
||||
|
||||
if (paramKey == L"id") {
|
||||
paramValue >> val;
|
||||
newChar->setId(val);
|
||||
} else if (paramKey == L"x") {
|
||||
paramValue >> val;
|
||||
newChar->setX(val);
|
||||
} else if (paramKey == L"y") {
|
||||
paramValue >> val;
|
||||
newChar->setY(val);
|
||||
} else if (paramKey == L"width") {
|
||||
paramValue >> val;
|
||||
newChar->setWidth(val);
|
||||
} else if (paramKey == L"height") {
|
||||
paramValue >> val;
|
||||
newChar->setHeight(val);
|
||||
} else if (paramKey == L"xoffset") {
|
||||
paramValue >> val;
|
||||
newChar->setXOffset(val);
|
||||
} else if (paramKey == L"yoffset") {
|
||||
paramValue >> val;
|
||||
newChar->setYOffset(val);
|
||||
} else if (paramKey == L"xadvance") {
|
||||
paramValue >> val;
|
||||
newChar->setXAdvance(val);
|
||||
}
|
||||
|
||||
// std::cout << "[" << paramKey << "] = '" << getParamValue(param) << "'" << std::endl;
|
||||
}
|
||||
|
||||
characters[newChar->getId()] = newChar;
|
||||
|
||||
} else {
|
||||
std::wstring dummy;
|
||||
getline(input, dummy);
|
||||
}
|
||||
}
|
||||
|
||||
if (!imageFile.empty() && imageWidth && imageHeight && !characters.empty()) {
|
||||
|
||||
// Load file and decode image.
|
||||
std::vector<unsigned char> image;
|
||||
|
||||
unsigned int imgWidth = imageWidth, imgHeight = imageHeight;
|
||||
|
||||
//1) First load the raw file to memory using wstring filenames
|
||||
wxFile png_file(imageFile);
|
||||
|
||||
int png_size = png_file.Length();
|
||||
|
||||
auto* raw_image = new unsigned char[png_size];
|
||||
|
||||
if (png_size > 0) {
|
||||
|
||||
int nbRead = png_file.Read((void*)raw_image, png_size);
|
||||
|
||||
if (png_size != nbRead) {
|
||||
|
||||
std::cout << "Error loading the full PNG image file in memory: '" << imageFile << "'" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
//2) then load from memory
|
||||
unsigned error = lodepng::decode(image, imgWidth, imgHeight, raw_image, png_size);
|
||||
|
||||
delete[] raw_image;
|
||||
png_file.Close();
|
||||
|
||||
if (error) {
|
||||
std::cout << "Error decoding PNG image file: '" << imageFile << "'" << std::endl;
|
||||
}
|
||||
|
||||
glGenTextures(1, &texId);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, texId);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, 4, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, error?nullptr:(&image[0]));
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
||||
std::map<int, GLFontChar *>::iterator char_i;
|
||||
|
||||
gl_vertices.resize(characters.size() * 8); // one quad per char
|
||||
gl_uv.resize(characters.size() * 8);
|
||||
|
||||
unsigned int ofs = 0;
|
||||
for (char_i = characters.begin(); char_i != characters.end(); char_i++) {
|
||||
// int charId = (*char_i).first;
|
||||
GLFontChar *fchar = (*char_i).second;
|
||||
|
||||
float faspect = fchar->getAspect();
|
||||
|
||||
float uv_xpos = (float) fchar->getX() / (float) imageWidth;
|
||||
float uv_ypos = ((float) fchar->getY() / (float) imageHeight);
|
||||
float uv_xofs = (float) fchar->getWidth() / (float) imageWidth;
|
||||
float uv_yofs = ((float) fchar->getHeight() / (float) imageHeight);
|
||||
|
||||
gl_vertices[ofs] = 0;
|
||||
gl_vertices[ofs + 1] = 0;
|
||||
gl_uv[ofs] = uv_xpos;
|
||||
gl_uv[ofs + 1] = uv_ypos + uv_yofs;
|
||||
|
||||
gl_vertices[ofs + 2] = faspect;
|
||||
gl_vertices[ofs + 3] = 0;
|
||||
gl_uv[ofs + 2] = uv_xpos + uv_xofs;
|
||||
gl_uv[ofs + 3] = uv_ypos + uv_yofs;
|
||||
|
||||
gl_vertices[ofs + 4] = faspect;
|
||||
gl_vertices[ofs + 5] = 1;
|
||||
gl_uv[ofs + 4] = uv_xpos + uv_xofs;
|
||||
gl_uv[ofs + 5] = uv_ypos;
|
||||
|
||||
gl_vertices[ofs + 6] = 0;
|
||||
gl_vertices[ofs + 7] = 1;
|
||||
gl_uv[ofs + 6] = uv_xpos;
|
||||
gl_uv[ofs + 7] = uv_ypos;
|
||||
|
||||
fchar->setIndex(ofs);
|
||||
|
||||
ofs += 8;
|
||||
}
|
||||
|
||||
std::cout << "Loaded font '" << fontName << "' from '" << imageFile << "', parsed " << characters.size() << " characters." << std::endl;
|
||||
|
||||
loaded = true;
|
||||
} else {
|
||||
std::cout << "Error loading font file " << imageFile << std::endl;
|
||||
}
|
||||
|
||||
input.close();
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
float GLFont::getStringWidth(const std::wstring& str, float size, float viewAspect) {
|
||||
|
||||
float scalex = size / viewAspect;
|
||||
|
||||
float width = 0;
|
||||
|
||||
for (int charId : str) {
|
||||
if (characters.find(charId) == characters.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GLFontChar *fchar = characters[charId];
|
||||
|
||||
float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
|
||||
float advx = (float) fchar->getXAdvance() / (float) imageWidth;
|
||||
|
||||
if (charId == 32) {
|
||||
advx = characters[L'_']->getAspect();
|
||||
}
|
||||
|
||||
width += fchar->getAspect() + advx + ofsx;
|
||||
}
|
||||
|
||||
width *= scalex;
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
// Draw string, immediate
|
||||
void GLFont::drawString(const std::wstring& str, int pxHeight, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) {
|
||||
|
||||
pxHeight *= 2;
|
||||
|
||||
if (!vpx || !vpy) {
|
||||
GLint vp[4];
|
||||
glGetIntegerv( GL_VIEWPORT, vp);
|
||||
vpx = vp[2];
|
||||
vpy = vp[3];
|
||||
}
|
||||
|
||||
if (cacheable) {
|
||||
gcCounter++;
|
||||
|
||||
std::lock_guard<SpinMutex> lock(cache_busy);
|
||||
|
||||
if (gcCounter > GC_DRAW_COUNT_PERIOD) {
|
||||
|
||||
doCacheGC();
|
||||
gcCounter = 0;
|
||||
}
|
||||
|
||||
GLFontStringCache *fc = nullptr;
|
||||
|
||||
std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
|
||||
|
||||
std::wstringstream sscacheIdx;
|
||||
|
||||
sscacheIdx << vpx << "." << vpy << "." << pxHeight << "." << str;
|
||||
|
||||
std::wstring cacheIdx(sscacheIdx.str());
|
||||
|
||||
cache_iter = stringCache.find(cacheIdx);
|
||||
if (cache_iter != stringCache.end()) {
|
||||
fc = cache_iter->second;
|
||||
fc->gc = 0;
|
||||
}
|
||||
|
||||
if (fc == nullptr) {
|
||||
// std::cout << "cache miss" << std::endl;
|
||||
fc = cacheString(str, pxHeight, vpx, vpy);
|
||||
stringCache[cacheIdx] = fc;
|
||||
}
|
||||
|
||||
drawCacheString(fc, xpos, ypos, hAlign, vAlign);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float size = (float) pxHeight / (float) vpy;
|
||||
float viewAspect = (float) vpx / (float) vpy;
|
||||
float msgWidth = getStringWidth(str, size, viewAspect);
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(xpos, ypos, 0.0f);
|
||||
|
||||
switch (vAlign) {
|
||||
case GLFONT_ALIGN_TOP:
|
||||
glTranslatef(0.0, -size, 0.0);
|
||||
break;
|
||||
case GLFONT_ALIGN_CENTER:
|
||||
glTranslatef(0.0, -size/2.0, 0.0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (hAlign) {
|
||||
case GLFONT_ALIGN_RIGHT:
|
||||
glTranslatef(-msgWidth, 0.0, 0.0);
|
||||
break;
|
||||
case GLFONT_ALIGN_CENTER:
|
||||
glTranslatef(-msgWidth / 2.0, 0.0, 0.0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
glPushMatrix();
|
||||
glScalef(size / viewAspect, size, 1.0f);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, texId);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glVertexPointer(2, GL_FLOAT, 0, &gl_vertices[0]);
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, &gl_uv[0]);
|
||||
|
||||
for (int charId : str) {
|
||||
if (characters.find(charId) == characters.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GLFontChar *fchar = characters[charId];
|
||||
|
||||
float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
|
||||
float advx = (float) fchar->getXAdvance() / (float) imageWidth;
|
||||
|
||||
if (charId == 32) {
|
||||
advx = characters[L'_']->getAspect();
|
||||
}
|
||||
|
||||
glTranslatef(ofsx, 0.0, 0.0);
|
||||
glDrawArrays(GL_QUADS, fchar->getIndex() / 2, 4);
|
||||
glTranslatef(fchar->getAspect() + advx, 0.0, 0.0);
|
||||
}
|
||||
|
||||
glVertexPointer(2, GL_FLOAT, 0, nullptr);
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, nullptr);
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glPopMatrix();
|
||||
glPopMatrix();
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
// Draw string, immediate, 8 bit version
|
||||
void GLFont::drawString(const std::string& str, int pxHeight, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) {
|
||||
|
||||
//Displayed string is wstring, so use wxString to do the heavy lifting of converting str...
|
||||
wxString wsTmp;
|
||||
|
||||
wsTmp.assign(str);
|
||||
|
||||
drawString(wsTmp.ToStdWstring(), pxHeight, xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
|
||||
}
|
||||
|
||||
// Draw cached GLFontCacheString
|
||||
void GLFont::drawCacheString(GLFontStringCache *fc, float xpos, float ypos, Align hAlign, Align vAlign) const {
|
||||
|
||||
float size = (float) fc->pxHeight / (float) fc->vpy;
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(xpos, ypos, 0.0f);
|
||||
|
||||
switch (vAlign) {
|
||||
case GLFONT_ALIGN_TOP:
|
||||
glTranslatef(0.0, -size, 0.0);
|
||||
break;
|
||||
case GLFONT_ALIGN_CENTER:
|
||||
glTranslatef(0.0, -size/2.0, 0.0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (hAlign) {
|
||||
case GLFONT_ALIGN_RIGHT:
|
||||
glTranslatef(-fc->msgWidth, 0.0, 0.0);
|
||||
break;
|
||||
case GLFONT_ALIGN_CENTER:
|
||||
glTranslatef(-fc->msgWidth / 2.0, 0.0, 0.0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, texId);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glVertexPointer(2, GL_FLOAT, 0, &fc->gl_vertices[0]);
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, &fc->gl_uv[0]);
|
||||
|
||||
glDrawArrays(GL_QUADS, 0, 4 * fc->drawlen);
|
||||
|
||||
glVertexPointer(2, GL_FLOAT, 0, nullptr);
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, nullptr);
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
// Compile optimized GLFontCacheString
|
||||
GLFontStringCache *GLFont::cacheString(const std::wstring& str, int pxHeight, int vpx, int vpy) {
|
||||
|
||||
auto *fc = new GLFontStringCache;
|
||||
|
||||
fc->pxHeight = pxHeight;
|
||||
fc->vpx = vpx;
|
||||
fc->vpy = vpy;
|
||||
|
||||
float size = (float) pxHeight / (float) vpy;
|
||||
float viewAspect = (float) vpx / (float) vpy;
|
||||
|
||||
fc->msgWidth = getStringWidth(str, size, viewAspect);
|
||||
|
||||
int nChar = 0;
|
||||
for (int charId : str) {
|
||||
if (characters.find(charId) == characters.end()) {
|
||||
continue;
|
||||
}
|
||||
nChar++;
|
||||
}
|
||||
|
||||
fc->drawlen = nChar;
|
||||
fc->gl_vertices.resize(nChar*8);
|
||||
fc->gl_uv.resize(nChar*8);
|
||||
|
||||
|
||||
CubicVR::mat4 trans = CubicVR::mat4::scale(size / viewAspect, size, 1.0f);
|
||||
|
||||
int c = 0;
|
||||
for (int charId : str) {
|
||||
if (characters.find(charId) == characters.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GLFontChar *fchar = characters[charId];
|
||||
|
||||
float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
|
||||
float advx = (float) fchar->getXAdvance() / (float) imageWidth;
|
||||
|
||||
if (charId == 32) {
|
||||
advx = characters[L'_']->getAspect();
|
||||
}
|
||||
|
||||
// freeze transform to buffer
|
||||
trans *= CubicVR::mat4::translate(ofsx, 0.0, 0.0);
|
||||
int charIdx = fchar->getIndex();
|
||||
for (int j = 0; j < 8; j+=2) {
|
||||
CubicVR::vec3 pt(gl_vertices[charIdx + j],gl_vertices[charIdx + j + 1], 0.0);
|
||||
pt = CubicVR::mat4::multiply(trans, pt, true);
|
||||
fc->gl_vertices[c * 8 + j] = pt[0];
|
||||
fc->gl_vertices[c * 8 + j + 1] = pt[1];
|
||||
fc->gl_uv[c * 8 + j] = gl_uv[charIdx + j];
|
||||
fc->gl_uv[c * 8 + j + 1] = gl_uv[charIdx + j + 1];
|
||||
}
|
||||
trans *= CubicVR::mat4::translate(fchar->getAspect() + advx, 0.0, 0.0);
|
||||
c++;
|
||||
}
|
||||
|
||||
return fc;
|
||||
}
|
||||
|
||||
void GLFont::doCacheGC() {
|
||||
|
||||
std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
|
||||
|
||||
bool flushDone = false;
|
||||
|
||||
//do aging and remove in one pass.
|
||||
cache_iter = stringCache.begin();
|
||||
|
||||
while (cache_iter != stringCache.end()) {
|
||||
|
||||
//aging
|
||||
cache_iter->second->gc--;
|
||||
|
||||
//only flush 1 element per call
|
||||
if (!flushDone && cache_iter->second->gc < -GC_DRAW_COUNT_LIMIT) {
|
||||
|
||||
delete cache_iter->second;
|
||||
cache_iter = stringCache.erase(cache_iter);
|
||||
flushDone = true;
|
||||
}
|
||||
else {
|
||||
cache_iter++;
|
||||
}
|
||||
} //end while
|
||||
}
|
||||
|
||||
void GLFont::clearCache() {
|
||||
|
||||
std::lock_guard<SpinMutex> lock(cache_busy);
|
||||
|
||||
std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
|
||||
|
||||
cache_iter = stringCache.begin();
|
||||
|
||||
while (cache_iter != stringCache.end()) {
|
||||
|
||||
delete cache_iter->second;
|
||||
cache_iter = stringCache.erase(cache_iter);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void GLFont::clearAllCaches() {
|
||||
|
||||
for (int i = 0; i < GLFont::GLFONT_SIZE_MAX; i++) {
|
||||
|
||||
fonts[i].clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GLFont::Drawer GLFont::getFont(int requestedSize, double scaleFactor) {
|
||||
|
||||
return GLFont::Drawer(requestedSize, scaleFactor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLFont::setScale(GLFontScale scale) {
|
||||
|
||||
//safety vs. inputs
|
||||
if (scale < GLFONT_SCALE_NORMAL || scale > GLFONT_SCALE_LARGE) {
|
||||
|
||||
scale = GLFontScale::GLFONT_SCALE_NORMAL;
|
||||
}
|
||||
|
||||
currentScale.store(scale);
|
||||
|
||||
//Flush all the GC stuff
|
||||
clearAllCaches();
|
||||
}
|
||||
|
||||
GLFont::GLFontScale GLFont::getScale() {
|
||||
|
||||
return currentScale.load();
|
||||
}
|
||||
|
||||
double GLFont::getScaleFactor() {
|
||||
|
||||
GLFontScale scale = currentScale.load();
|
||||
|
||||
if (scale == GLFONT_SCALE_MEDIUM) {
|
||||
|
||||
return 1.5;
|
||||
}
|
||||
else if (scale == GLFONT_SCALE_LARGE) {
|
||||
|
||||
return 2.0;
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
int GLFont::getScaledPx(int basicFontSize, double scaleFactor) {
|
||||
//try to align on an integer pixel size if the targetSize font is available
|
||||
int targetSize = round(basicFontSize * scaleFactor);
|
||||
int resultIndex = 0;
|
||||
|
||||
fonts[0].loadFontOnce();
|
||||
|
||||
for (int i = 0; i < GLFONT_SIZE_MAX - 1; i++) {
|
||||
|
||||
fonts[i + 1].loadFontOnce();
|
||||
|
||||
if (fonts[i + 1].pixHeight <= targetSize) {
|
||||
resultIndex = i + 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
} //end for
|
||||
|
||||
// return font height px
|
||||
return fonts[resultIndex].pixHeight;
|
||||
}
|
||||
|
||||
|
||||
GLFont::Drawer::Drawer(int basicFontSize, double scaleFactor) {
|
||||
|
||||
//Selection of the final font: scan GLFont::fonts to find the biggest font such as
|
||||
// its pixHeight <= basicFontSize * scaleFactor.
|
||||
//then compute finalScaleFactor the zooming factor of renderingFont to reach a
|
||||
//final font size of basicFontSize* scaleFactor:
|
||||
renderingFontIndex = 0;
|
||||
|
||||
//try to align on an integer pixel size if the targetSize font is available
|
||||
int targetSize = round(basicFontSize * scaleFactor);
|
||||
|
||||
fonts[0].loadFontOnce();
|
||||
|
||||
for (int i = 0; i < GLFONT_SIZE_MAX - 1; i++) {
|
||||
|
||||
fonts[i + 1].loadFontOnce();
|
||||
|
||||
if (fonts[i + 1].pixHeight <= targetSize) {
|
||||
|
||||
renderingFontIndex = i + 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
} //end for
|
||||
|
||||
//
|
||||
int rawSize = fonts[renderingFontIndex].pixHeight;
|
||||
|
||||
//targetSize may not be reached yet, so the effective rendering font: fonts[renderingFontIndex] must be scaled up a bit.
|
||||
renderingFontScaleFactor = (double) targetSize / rawSize;
|
||||
}
|
||||
|
||||
void GLFont::Drawer::drawString(const std::wstring& str, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) const {
|
||||
|
||||
GLFont& appliedFont = fonts[renderingFontIndex];
|
||||
|
||||
appliedFont.drawString(str, round(appliedFont.pixHeight * renderingFontScaleFactor), xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
|
||||
}
|
||||
|
||||
//Public drawing font, 8 bit char version.
|
||||
void GLFont::Drawer::drawString(const std::string& str, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) const {
|
||||
|
||||
GLFont& appliedFont = fonts[renderingFontIndex];
|
||||
|
||||
appliedFont.drawString(str, round(appliedFont.pixHeight * renderingFontScaleFactor), xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
|
||||
}
|
||||
|
209
Software/CubicSDR/src/util/GLFont.h
Normal file
209
Software/CubicSDR/src/util/GLFont.h
Normal file
@ -0,0 +1,209 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include "lodepng.h"
|
||||
#include "wx/glcanvas.h"
|
||||
#include "wx/filename.h"
|
||||
#include "wx/stdpaths.h"
|
||||
|
||||
#include "SpinMutex.h"
|
||||
|
||||
class GLFontStringCache {
|
||||
public:
|
||||
GLFontStringCache();
|
||||
int drawlen;
|
||||
int vpx, vpy;
|
||||
int pxHeight = 0;
|
||||
float msgWidth = 0.0f;
|
||||
std::atomic_int gc;
|
||||
std::vector<float> gl_vertices;
|
||||
std::vector<float> gl_uv;
|
||||
};
|
||||
|
||||
class GLFontChar {
|
||||
public:
|
||||
GLFontChar();
|
||||
~GLFontChar();
|
||||
|
||||
void setId(int idval);
|
||||
|
||||
// Returns the code point of the 16bit character, supposely Unicode.
|
||||
int getId() const;
|
||||
|
||||
void setXOffset(int xofs);
|
||||
int getXOffset();
|
||||
|
||||
void setYOffset(int yofs);
|
||||
int getYOffset();
|
||||
|
||||
void setX(int xpos);
|
||||
int getX();
|
||||
|
||||
void setY(int ypos);
|
||||
int getY();
|
||||
|
||||
void setWidth(int w);
|
||||
int getWidth();
|
||||
|
||||
void setHeight(int h);
|
||||
int getHeight() const;
|
||||
|
||||
void setXAdvance(int xadv);
|
||||
int getXAdvance();
|
||||
|
||||
float getAspect() const;
|
||||
|
||||
void setIndex(unsigned int idx);
|
||||
int getIndex() const;
|
||||
|
||||
private:
|
||||
// this is the code point of the 16bit character, supposly Unicode.
|
||||
int id;
|
||||
int x, y, width, height;
|
||||
int xoffset, yoffset;
|
||||
int xadvance;
|
||||
float aspect;
|
||||
int index;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class GLFont {
|
||||
public:
|
||||
|
||||
enum Align {
|
||||
GLFONT_ALIGN_LEFT, GLFONT_ALIGN_RIGHT, GLFONT_ALIGN_CENTER, GLFONT_ALIGN_TOP, GLFONT_ALIGN_BOTTOM
|
||||
};
|
||||
enum GLFontSize {
|
||||
GLFONT_SIZE12,
|
||||
GLFONT_SIZE16,
|
||||
GLFONT_SIZE18,
|
||||
GLFONT_SIZE24,
|
||||
GLFONT_SIZE27, //new
|
||||
GLFONT_SIZE32,
|
||||
GLFONT_SIZE36, //new
|
||||
GLFONT_SIZE48,
|
||||
GLFONT_SIZE64, //new
|
||||
GLFONT_SIZE72, //new
|
||||
GLFONT_SIZE96, //new
|
||||
GLFONT_SIZE_MAX
|
||||
};
|
||||
|
||||
enum GLFontScale {
|
||||
GLFONT_SCALE_NORMAL,
|
||||
GLFONT_SCALE_MEDIUM, // x1.5
|
||||
GLFONT_SCALE_LARGE, // x2
|
||||
GLFONT_SCALE_MAX
|
||||
};
|
||||
|
||||
GLFont(GLFontSize size, std::wstring fontFileName);
|
||||
~GLFont();
|
||||
|
||||
|
||||
//Called to change the scale of the rendered fonts
|
||||
static void setScale(GLFontScale scale);
|
||||
|
||||
static GLFontScale getScale();
|
||||
|
||||
//Mean current scale factor: 1.0 in normal, 1.5 medium, 2.0 for large
|
||||
static double getScaleFactor();
|
||||
|
||||
//Return a valid font px height given the font size and scale factor
|
||||
static int getScaledPx(int basicFontSize, double scaleFactor);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
std::wstring nextParam(std::wistringstream &str);
|
||||
std::wstring getParamKey(const std::wstring& param_str);
|
||||
std::wstring getParamValue(const std::wstring& param_str);
|
||||
|
||||
//Repository of all loaded fonts
|
||||
static GLFont fonts[GLFontSize::GLFONT_SIZE_MAX];
|
||||
|
||||
static std::atomic<GLFontScale> currentScale;
|
||||
|
||||
//load a given font file, (lazy loading)
|
||||
void loadFontOnce();
|
||||
|
||||
//private drawing font, 16 bit char version, called by Drawer object
|
||||
void drawString(const std::wstring& str, int pxHeight, float xpos, float ypos, Align hAlign = GLFONT_ALIGN_LEFT, Align vAlign = GLFONT_ALIGN_TOP, int vpx = 0, int vpy = 0, bool cacheable = false);
|
||||
|
||||
//private drawing font, 8 bit char version, called by Drawer object
|
||||
void drawString(const std::string& str, int pxHeight, float xpos, float ypos, Align hAlign = GLFONT_ALIGN_LEFT, Align vAlign = GLFONT_ALIGN_TOP, int vpx = 0, int vpy = 0, bool cacheable = false);
|
||||
|
||||
GLFontStringCache *cacheString(const std::wstring& str, int pxHeight, int vpx, int vpy);
|
||||
void drawCacheString(GLFontStringCache *fc, float xpos, float ypos, Align hAlign, Align vAlign) const;
|
||||
|
||||
void doCacheGC();
|
||||
void clearCache();
|
||||
|
||||
//force GC of all available fonts
|
||||
static void clearAllCaches();
|
||||
|
||||
float getStringWidth(const std::wstring& str, float size, float viewAspect);
|
||||
|
||||
//the string cache is per-front (internal font)
|
||||
std::map<std::wstring, GLFontStringCache * > stringCache;
|
||||
|
||||
int lineHeight;
|
||||
int base;
|
||||
int imageWidth, imageHeight, pixHeight;
|
||||
bool loaded;
|
||||
GLFontSize fontSizeClass;
|
||||
|
||||
std::map<int, GLFontChar *> characters;
|
||||
|
||||
std::vector<float> gl_vertices;
|
||||
std::vector<float> gl_uv;
|
||||
|
||||
//The font name as written in the def file.
|
||||
std::wstring fontName;
|
||||
|
||||
//The full path font PNG filename
|
||||
std::wstring imageFile;
|
||||
|
||||
//the real path location of the font definition file
|
||||
std::wstring fontDefFileSource;
|
||||
|
||||
GLuint texId;
|
||||
int gcCounter;
|
||||
SpinMutex cache_busy;
|
||||
|
||||
public:
|
||||
|
||||
//Proxy class computing and caching the selection of the underlying fonts
|
||||
//depending of the user input and requested scale for the fonts.
|
||||
class Drawer {
|
||||
|
||||
private:
|
||||
|
||||
//result of the computation
|
||||
int renderingFontIndex = 0;
|
||||
|
||||
double renderingFontScaleFactor = 1.0;
|
||||
|
||||
public:
|
||||
|
||||
Drawer(int basicFontSize, double scaleFactor);
|
||||
|
||||
//Public drawing font, 16 bit char version.
|
||||
void drawString(const std::wstring& str, float xpos, float ypos, Align hAlign = GLFONT_ALIGN_LEFT, Align vAlign = GLFONT_ALIGN_TOP, int vpx = 0, int vpy = 0, bool cacheable = false) const;
|
||||
|
||||
//Public drawing font, 8 bit char version.
|
||||
void drawString(const std::string& str, float xpos, float ypos, Align hAlign = GLFONT_ALIGN_LEFT, Align vAlign = GLFONT_ALIGN_TOP, int vpx = 0, int vpy = 0, bool cacheable = false) const;
|
||||
|
||||
}; //end class Drawer
|
||||
|
||||
//The User request a font of size requestedSize to display, with an additional
|
||||
//optional scale factor scaleFactor.
|
||||
static GLFont::Drawer getFont(int requestedSize, double scaleFactor = 1.0);
|
||||
|
||||
};
|
87
Software/CubicSDR/src/util/Gradient.cpp
Normal file
87
Software/CubicSDR/src/util/Gradient.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include "Gradient.h"
|
||||
#include <cstddef>
|
||||
|
||||
Gradient::Gradient() = default;
|
||||
|
||||
void Gradient::clear() {
|
||||
colors.clear();
|
||||
}
|
||||
|
||||
void Gradient::addColor(GradientColor c) {
|
||||
colors.push_back(c);
|
||||
}
|
||||
|
||||
void Gradient::addColors(const std::vector<GradientColor>& color_list) {
|
||||
|
||||
for (auto single_color : color_list) {
|
||||
|
||||
colors.push_back(single_color);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> &Gradient::getRed() {
|
||||
return r_val;
|
||||
}
|
||||
|
||||
std::vector<float> &Gradient::getGreen() {
|
||||
return g_val;
|
||||
}
|
||||
|
||||
std::vector<float> &Gradient::getBlue() {
|
||||
return b_val;
|
||||
}
|
||||
|
||||
void Gradient::generate(unsigned int len) {
|
||||
size_t chunk_size = len / (colors.size() - 1);
|
||||
|
||||
size_t p = 0;
|
||||
r_val.resize(len);
|
||||
g_val.resize(len);
|
||||
b_val.resize(len);
|
||||
|
||||
for (size_t j = 0, jMax = colors.size() - 1; j < jMax; j++) {
|
||||
if ((chunk_size * (jMax)) < len && (j == jMax - 1)) {
|
||||
chunk_size += len - chunk_size * (jMax);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < chunk_size; i++) {
|
||||
float idx = (float) (i) / (float) chunk_size;
|
||||
|
||||
float r1 = colors[j].r;
|
||||
float g1 = colors[j].g;
|
||||
float b1 = colors[j].b;
|
||||
|
||||
float r2 = colors[j + 1].r;
|
||||
float g2 = colors[j + 1].g;
|
||||
float b2 = colors[j + 1].b;
|
||||
|
||||
float r = r1 + (r2 - r1) * idx;
|
||||
float g = g1 + (g2 - g1) * idx;
|
||||
float b = b1 + (b2 - b1) * idx;
|
||||
|
||||
if (r < 0.0)
|
||||
r = 0.0;
|
||||
if (r > 1.0)
|
||||
r = 1.0;
|
||||
if (g < 0.0)
|
||||
g = 0.0;
|
||||
if (g > 1.0)
|
||||
g = 1.0;
|
||||
if (b < 0.0)
|
||||
b = 0.0;
|
||||
if (b > 1.0)
|
||||
b = 1.0;
|
||||
|
||||
r_val[p] = r;
|
||||
g_val[p] = g;
|
||||
b_val[p] = b;
|
||||
|
||||
p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gradient::~Gradient() = default;
|
41
Software/CubicSDR/src/util/Gradient.h
Normal file
41
Software/CubicSDR/src/util/Gradient.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
class GradientColor {
|
||||
public:
|
||||
float r, g, b;
|
||||
float w;
|
||||
|
||||
//should work with both float and double inputs
|
||||
GradientColor(double r_in, double g_in, double b_in) :
|
||||
r((float)r_in), g((float)g_in), b((float)b_in), w(1) {
|
||||
}
|
||||
};
|
||||
|
||||
class Gradient {
|
||||
public:
|
||||
Gradient();
|
||||
|
||||
void addColor(GradientColor c);
|
||||
|
||||
void clear();
|
||||
|
||||
void addColors(const std::vector<GradientColor>& color_list);
|
||||
|
||||
std::vector<float> &getRed();
|
||||
std::vector<float> &getGreen();
|
||||
std::vector<float> &getBlue();
|
||||
|
||||
void generate(unsigned int len);
|
||||
|
||||
~Gradient();
|
||||
private:
|
||||
std::vector<GradientColor> colors;
|
||||
std::vector<float> r_val;
|
||||
std::vector<float> g_val;
|
||||
std::vector<float> b_val;
|
||||
};
|
183
Software/CubicSDR/src/util/MouseTracker.cpp
Normal file
183
Software/CubicSDR/src/util/MouseTracker.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include "MouseTracker.h"
|
||||
|
||||
MouseTracker::MouseTracker(wxWindow *target) :
|
||||
mouseX(0), mouseY(0), lastMouseX(0), lastMouseY(0), originMouseX(0), originMouseY(0), deltaMouseX(0), deltaMouseY(0), vertDragLock(false), horizDragLock(
|
||||
false), isMouseDown(false), isMouseRightDown(false), isMouseInView(false), target(target) {
|
||||
|
||||
}
|
||||
|
||||
MouseTracker::MouseTracker() :
|
||||
MouseTracker(nullptr) {
|
||||
|
||||
}
|
||||
|
||||
void MouseTracker::OnMouseMoved(wxMouseEvent& event) {
|
||||
if (target == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wxSize ClientSize = target->GetClientSize();
|
||||
|
||||
mouseX = (float) event.m_x / (float) ClientSize.x;
|
||||
mouseY = 1.0 - (float) event.m_y / (float) ClientSize.y;
|
||||
|
||||
deltaMouseX = mouseX - lastMouseX;
|
||||
deltaMouseY = mouseY - lastMouseY;
|
||||
|
||||
if (isMouseDown || isMouseRightDown) {
|
||||
#ifndef __APPLE__
|
||||
#if !defined(__linux__) && !defined(__FreeBSD__)
|
||||
if (horizDragLock && vertDragLock) {
|
||||
target->WarpPointer(originMouseX * ClientSize.x, (1.0 - originMouseY) * ClientSize.y);
|
||||
mouseX = originMouseX;
|
||||
mouseY = originMouseY;
|
||||
} else if (vertDragLock && mouseY != lastMouseY) {
|
||||
target->WarpPointer(event.m_x, (1.0 - originMouseY) * ClientSize.y);
|
||||
mouseY = originMouseY;
|
||||
} else if (horizDragLock && mouseX != lastMouseX) {
|
||||
target->WarpPointer(originMouseX * ClientSize.x, event.m_y);
|
||||
mouseX = originMouseX;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
lastMouseX = mouseX;
|
||||
lastMouseY = mouseY;
|
||||
}
|
||||
|
||||
void MouseTracker::OnMouseWheelMoved(wxMouseEvent& /* event */) {
|
||||
// std::cout << "wheel?" << std::endl;
|
||||
}
|
||||
|
||||
void MouseTracker::OnMouseDown(wxMouseEvent& event) {
|
||||
if (isMouseRightDown || target == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wxSize ClientSize = target->GetClientSize();
|
||||
|
||||
mouseX = lastMouseX = (float) event.m_x / (float) ClientSize.x;
|
||||
mouseY = lastMouseY = 1.0 - (float) event.m_y / (float) ClientSize.y;
|
||||
|
||||
originMouseX = mouseX;
|
||||
originMouseY = mouseY;
|
||||
|
||||
isMouseDown = true;
|
||||
}
|
||||
|
||||
void MouseTracker::OnMouseReleased(wxMouseEvent& /* event */) {
|
||||
isMouseDown = false;
|
||||
}
|
||||
|
||||
void MouseTracker::OnMouseRightDown(wxMouseEvent& event) {
|
||||
if (isMouseDown || target == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wxSize ClientSize = target->GetClientSize();
|
||||
|
||||
mouseX = lastMouseX = (float) event.m_x / (float) ClientSize.x;
|
||||
mouseY = lastMouseY = 1.0 - (float) event.m_y / (float) ClientSize.y;
|
||||
|
||||
originMouseX = mouseX;
|
||||
originMouseY = mouseY;
|
||||
|
||||
isMouseRightDown = true;
|
||||
}
|
||||
|
||||
void MouseTracker::OnMouseRightReleased(wxMouseEvent& /* event */) {
|
||||
isMouseRightDown = false;
|
||||
}
|
||||
|
||||
void MouseTracker::OnMouseEnterWindow(wxMouseEvent& /* event */) {
|
||||
isMouseInView = true;
|
||||
isMouseDown = false;
|
||||
isMouseRightDown = false;
|
||||
}
|
||||
|
||||
void MouseTracker::OnMouseLeftWindow(wxMouseEvent& /* event */) {
|
||||
isMouseDown = false;
|
||||
isMouseRightDown = false;
|
||||
isMouseInView = false;
|
||||
}
|
||||
|
||||
float MouseTracker::getOriginMouseX() const {
|
||||
return originMouseX;
|
||||
}
|
||||
|
||||
float MouseTracker::getOriginMouseY() const {
|
||||
return originMouseY;
|
||||
}
|
||||
|
||||
float MouseTracker::getOriginDeltaMouseX() const {
|
||||
return mouseX - originMouseX;
|
||||
}
|
||||
|
||||
float MouseTracker::getOriginDeltaMouseY() const {
|
||||
return mouseY - originMouseY;
|
||||
}
|
||||
|
||||
float MouseTracker::getDeltaMouseX() const {
|
||||
return deltaMouseX;
|
||||
}
|
||||
|
||||
float MouseTracker::getDeltaMouseY() const {
|
||||
return deltaMouseY;
|
||||
}
|
||||
|
||||
float MouseTracker::getLastMouseX() const {
|
||||
return lastMouseX;
|
||||
}
|
||||
|
||||
float MouseTracker::getLastMouseY() const {
|
||||
return lastMouseY;
|
||||
}
|
||||
|
||||
CubicVR::vec2 MouseTracker::getGLXY() const {
|
||||
return CubicVR::vec2((getMouseX()-0.5)*2.0, (getMouseY()-0.5)*2.0);
|
||||
}
|
||||
|
||||
float MouseTracker::getMouseX() const {
|
||||
return mouseX;
|
||||
}
|
||||
|
||||
float MouseTracker::getMouseY() const {
|
||||
return mouseY;
|
||||
}
|
||||
|
||||
void MouseTracker::setVertDragLock(bool dragLock) {
|
||||
vertDragLock = dragLock;
|
||||
}
|
||||
|
||||
void MouseTracker::setHorizDragLock(bool dragLock) {
|
||||
horizDragLock = dragLock;
|
||||
}
|
||||
|
||||
bool MouseTracker::getVertDragLock() const {
|
||||
return vertDragLock;
|
||||
}
|
||||
|
||||
bool MouseTracker::getHorizDragLock() const {
|
||||
return horizDragLock;
|
||||
}
|
||||
|
||||
bool MouseTracker::mouseDown() const {
|
||||
return isMouseDown;
|
||||
}
|
||||
|
||||
bool MouseTracker::mouseInView() const {
|
||||
return isMouseInView;
|
||||
}
|
||||
|
||||
void MouseTracker::setTarget(wxWindow *target_in) {
|
||||
target = target_in;
|
||||
}
|
||||
|
||||
|
||||
bool MouseTracker::mouseRightDown() const {
|
||||
return isMouseRightDown;
|
||||
}
|
53
Software/CubicSDR/src/util/MouseTracker.h
Normal file
53
Software/CubicSDR/src/util/MouseTracker.h
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wx/window.h"
|
||||
#include "cubic_math.h"
|
||||
|
||||
class MouseTracker {
|
||||
public:
|
||||
MouseTracker(wxWindow *target);
|
||||
MouseTracker();
|
||||
|
||||
void OnMouseMoved(wxMouseEvent& event);
|
||||
void OnMouseWheelMoved(wxMouseEvent& event);
|
||||
void OnMouseDown(wxMouseEvent& event);
|
||||
void OnMouseReleased(wxMouseEvent& event);
|
||||
void OnMouseRightDown(wxMouseEvent& event);
|
||||
void OnMouseRightReleased(wxMouseEvent& event);
|
||||
void OnMouseEnterWindow(wxMouseEvent& event);
|
||||
void OnMouseLeftWindow(wxMouseEvent& event);
|
||||
|
||||
float getOriginMouseX() const;
|
||||
float getOriginMouseY() const;
|
||||
float getOriginDeltaMouseX() const;
|
||||
float getOriginDeltaMouseY() const;
|
||||
float getDeltaMouseX() const;
|
||||
float getDeltaMouseY() const;
|
||||
float getLastMouseX() const;
|
||||
float getLastMouseY() const;
|
||||
CubicVR::vec2 getGLXY() const;
|
||||
float getMouseX() const;
|
||||
float getMouseY() const;
|
||||
|
||||
void setVertDragLock(bool dragLock);
|
||||
void setHorizDragLock(bool dragLock);
|
||||
bool getVertDragLock() const;
|
||||
bool getHorizDragLock() const;
|
||||
bool mouseDown() const;
|
||||
bool mouseRightDown() const;
|
||||
bool mouseInView() const;
|
||||
void setTarget(wxWindow *target_in);
|
||||
|
||||
private:
|
||||
float mouseX, mouseY;
|
||||
float lastMouseX, lastMouseY;
|
||||
float originMouseX, originMouseY;
|
||||
float deltaMouseX, deltaMouseY;
|
||||
|
||||
bool vertDragLock, horizDragLock;
|
||||
bool isMouseDown, isMouseRightDown, isMouseInView;
|
||||
wxWindow *target;
|
||||
};
|
27
Software/CubicSDR/src/util/SpinMutex.h
Normal file
27
Software/CubicSDR/src/util/SpinMutex.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
|
||||
// A non-recursive Mutex implemented as a spin-lock, implementing the Lockable requirement
|
||||
class SpinMutex {
|
||||
|
||||
public:
|
||||
SpinMutex() = default;
|
||||
|
||||
SpinMutex(const SpinMutex&) = delete;
|
||||
|
||||
SpinMutex& operator=(const SpinMutex&) = delete;
|
||||
|
||||
~SpinMutex() { lock_state.clear(std::memory_order_release); }
|
||||
|
||||
void lock() { while (lock_state.test_and_set(std::memory_order_acquire)); }
|
||||
|
||||
bool try_lock() {return !lock_state.test_and_set(std::memory_order_acquire); }
|
||||
|
||||
void unlock() { lock_state.clear(std::memory_order_release); }
|
||||
|
||||
private:
|
||||
std::atomic_flag lock_state = ATOMIC_FLAG_INIT;
|
||||
};
|
4
Software/CubicSDR/src/util/ThreadBlockingQueue.cpp
Normal file
4
Software/CubicSDR/src/util/ThreadBlockingQueue.cpp
Normal file
@ -0,0 +1,4 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include <ThreadBlockingQueue.h>
|
229
Software/CubicSDR/src/util/ThreadBlockingQueue.h
Normal file
229
Software/CubicSDR/src/util/ThreadBlockingQueue.h
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <condition_variable>
|
||||
#include <typeinfo>
|
||||
#include <iostream>
|
||||
#include "SpinMutex.h"
|
||||
|
||||
#define MIN_ITEM_NB (1)
|
||||
|
||||
//use this timeout constant in either pop() or push() calls to indicate
|
||||
// a non-blocking operation, so respectively equivalent to try_pop() and try_push()
|
||||
#define NON_BLOCKING_TIMEOUT (100)
|
||||
|
||||
//use this timeout constant in either pop() or push() calls to indicate
|
||||
//an indefnite timeout duration.
|
||||
#define BLOCKING_INFINITE_TIMEOUT (0)
|
||||
|
||||
class ThreadQueueBase {
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<ThreadQueueBase> ThreadQueueBasePtr;
|
||||
|
||||
/** A thread-safe asynchronous blocking queue */
|
||||
template<typename T>
|
||||
class ThreadBlockingQueue : public ThreadQueueBase {
|
||||
|
||||
typedef typename std::deque<T>::value_type value_type;
|
||||
typedef typename std::deque<T>::size_type size_type;
|
||||
|
||||
public:
|
||||
|
||||
/*! Create safe blocking queue. */
|
||||
ThreadBlockingQueue() {
|
||||
//at least 1 (== Java SynchronizedQueue)
|
||||
m_max_num_items = MIN_ITEM_NB;
|
||||
};
|
||||
|
||||
//Forbid Copy construction.
|
||||
ThreadBlockingQueue(const ThreadBlockingQueue& sq) = delete;
|
||||
|
||||
/*! Forbid copy assignment. */
|
||||
ThreadBlockingQueue& operator=(const ThreadBlockingQueue& sq) = delete;
|
||||
|
||||
/*! Destroy safe queue. */
|
||||
~ThreadBlockingQueue() {
|
||||
std::lock_guard < SpinMutex > lock(m_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of items in the queue. Real value is clamped
|
||||
* to 1 on the lower bound.
|
||||
* \param[in] nb max of items
|
||||
*/
|
||||
void set_max_num_items(unsigned int max_num_items) {
|
||||
std::lock_guard < SpinMutex > lock(m_mutex);
|
||||
|
||||
if (max_num_items > m_max_num_items) {
|
||||
//Only raise the existing max size, never reduce it
|
||||
//for simplification sake at runtime.
|
||||
m_max_num_items = max_num_items;
|
||||
m_cond_not_full.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the item into the queue. If the queue is full, waits until room
|
||||
* is available, for at most timeout microseconds.
|
||||
* \param[in] item An item.
|
||||
* \param[in] timeout a max waiting timeout in microseconds for an item to be pushed.
|
||||
* by default, = 0 means indefinite wait.
|
||||
* \param[in] errorMessage if != nullptr (is nullptr by default) an error message written on std::cout in case of the timeout wait
|
||||
* \return true if an item was pushed into the queue, else a timeout has occurred.
|
||||
*/
|
||||
bool push(const value_type& item, std::uint64_t timeout = BLOCKING_INFINITE_TIMEOUT,const char* errorMessage = nullptr) {
|
||||
std::unique_lock < SpinMutex > lock(m_mutex);
|
||||
|
||||
if (timeout == BLOCKING_INFINITE_TIMEOUT) {
|
||||
m_cond_not_full.wait(lock, [this]() // Lambda funct
|
||||
{
|
||||
return m_queue.size() < m_max_num_items;
|
||||
});
|
||||
} else if (timeout <= NON_BLOCKING_TIMEOUT && m_queue.size() >= m_max_num_items) {
|
||||
// if the value is below a threshold, consider it is a try_push()
|
||||
return false;
|
||||
}
|
||||
else if (false == m_cond_not_full.wait_for(lock, std::chrono::microseconds(timeout),
|
||||
[this]() { return m_queue.size() < m_max_num_items; })) {
|
||||
|
||||
if (errorMessage != nullptr) {
|
||||
std::thread::id currentThreadId = std::this_thread::get_id();
|
||||
std::cout << "WARNING: Thread 0x" << std::hex << currentThreadId << std::dec <<
|
||||
" (" << currentThreadId << ") executing {" << typeid(*this).name() << "}.push() has failed with timeout > " <<
|
||||
(timeout * 0.001) << " ms, message: '" << errorMessage << "'" << std::endl << std::flush;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
m_queue.push_back(item);
|
||||
m_cond_not_empty.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to pushes the item into the queue, immediately, without waiting. If the queue is full, the item
|
||||
* is not inserted and the function returns false.
|
||||
* \param[in] item An item.
|
||||
*/
|
||||
bool try_push(const value_type& item) {
|
||||
std::lock_guard < SpinMutex > lock(m_mutex);
|
||||
|
||||
if (m_queue.size() >= m_max_num_items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_queue.push_back(item);
|
||||
m_cond_not_empty.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops item from the queue. If the queue is empty, blocks for timeout microseconds, or until item becomes available.
|
||||
* \param[in] timeout The number of microseconds to wait. O (default) means indefinite wait.
|
||||
* \param[in] errorMessage if != nullptr (is nullptr by default) an error message written on std::cout in case of the timeout wait
|
||||
* \return true if get an item from the queue, false if no item is received before the timeout.
|
||||
*/
|
||||
bool pop(value_type& item, std::uint64_t timeout = BLOCKING_INFINITE_TIMEOUT, const char* errorMessage = nullptr) {
|
||||
std::unique_lock < SpinMutex > lock(m_mutex);
|
||||
|
||||
if (timeout == BLOCKING_INFINITE_TIMEOUT) {
|
||||
m_cond_not_empty.wait(lock, [this]() // Lambda funct
|
||||
{
|
||||
return !m_queue.empty();
|
||||
});
|
||||
} else if (timeout <= NON_BLOCKING_TIMEOUT && m_queue.empty()) {
|
||||
// if the value is below a threshold, consider it is try_pop()
|
||||
return false;
|
||||
}
|
||||
else if (false == m_cond_not_empty.wait_for(lock, std::chrono::microseconds(timeout),
|
||||
[this]() { return !m_queue.empty(); })) {
|
||||
|
||||
if (errorMessage != nullptr) {
|
||||
std::thread::id currentThreadId = std::this_thread::get_id();
|
||||
std::cout << "WARNING: Thread 0x" << std::hex << currentThreadId << std::dec <<
|
||||
" (" << currentThreadId << ") executing {" << typeid(*this).name() << "}.pop() has failed with timeout > " <<
|
||||
(timeout * 0.001) << " ms, message: '" << errorMessage << "'" << std::endl << std::flush;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
item = m_queue.front();
|
||||
m_queue.pop_front();
|
||||
m_cond_not_full.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to pop item from the queue.
|
||||
* \param[out] item The item.
|
||||
* \return False is returned if no item is available.
|
||||
*/
|
||||
bool try_pop(value_type& item) {
|
||||
std::lock_guard < SpinMutex > lock(m_mutex);
|
||||
|
||||
if (m_queue.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
item = m_queue.front();
|
||||
m_queue.pop_front();
|
||||
m_cond_not_full.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of items in the queue.
|
||||
* \return Number of items in the queue.
|
||||
*/
|
||||
size_type size() const {
|
||||
std::lock_guard < SpinMutex > lock(m_mutex);
|
||||
return m_queue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the queue is empty.
|
||||
* \return true if queue is empty.
|
||||
*/
|
||||
bool empty() const {
|
||||
std::lock_guard < SpinMutex > lock(m_mutex);
|
||||
return m_queue.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the queue is full.
|
||||
* \return true if queue is full.
|
||||
*/
|
||||
bool full() const {
|
||||
std::lock_guard < SpinMutex > lock(m_mutex);
|
||||
return (m_queue.size() >= m_max_num_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any items in the queue.
|
||||
*/
|
||||
void flush() {
|
||||
std::lock_guard < SpinMutex > lock(m_mutex);
|
||||
m_queue.clear();
|
||||
m_cond_not_full.notify_all();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
std::deque<T> m_queue;
|
||||
|
||||
mutable SpinMutex m_mutex;
|
||||
std::condition_variable_any m_cond_not_empty;
|
||||
std::condition_variable_any m_cond_not_full;
|
||||
size_t m_max_num_items = MIN_ITEM_NB;
|
||||
};
|
182
Software/CubicSDR/src/util/Timer.cpp
Normal file
182
Software/CubicSDR/src/util/Timer.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include <iostream>
|
||||
#include "Timer.h"
|
||||
|
||||
Timer::Timer() : time_elapsed(0), system_milliseconds(0), start_time(0), end_time(0), last_update(0), num_updates(0), paused_time(0), offset(0), paused_state(false), lock_state(false), lock_rate(0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// According to Microsoft, QueryPerformanceXXX API is perfectly
|
||||
//fine for Windows 7+ systems, and use the highest appropriate counter.
|
||||
//this only need to be done once.
|
||||
::QueryPerformanceFrequency(&win_frequency);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void Timer::start()
|
||||
{
|
||||
update();
|
||||
num_updates = 0;
|
||||
start_time = system_milliseconds;
|
||||
last_update = start_time;
|
||||
paused_state = false;
|
||||
lock_state = false;
|
||||
lock_rate = 0;
|
||||
paused_time = 0;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
|
||||
void Timer::stop()
|
||||
{
|
||||
end_time = system_milliseconds;
|
||||
}
|
||||
|
||||
|
||||
void Timer::reset()
|
||||
{
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
void Timer::lockFramerate(float f_rate)
|
||||
{
|
||||
lock_rate = 1.0f/f_rate;
|
||||
lock_state = true;
|
||||
}
|
||||
|
||||
|
||||
void Timer::unlock()
|
||||
{
|
||||
unsigned long msec_tmp = system_milliseconds;
|
||||
|
||||
lock_state = false;
|
||||
|
||||
update();
|
||||
|
||||
last_update = system_milliseconds-(unsigned long)lock_rate;
|
||||
|
||||
offset += msec_tmp-system_milliseconds;
|
||||
|
||||
lock_rate = 0;
|
||||
}
|
||||
|
||||
bool Timer::locked() const
|
||||
{
|
||||
return lock_state;
|
||||
}
|
||||
|
||||
void Timer::update()
|
||||
{
|
||||
num_updates++;
|
||||
last_update = system_milliseconds;
|
||||
|
||||
|
||||
if (lock_state)
|
||||
{
|
||||
system_milliseconds += (unsigned long)(lock_rate*1000.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
||||
//Use QuaryPerformanceCounter, imune to problems sometimes
|
||||
//multimedia timers have.
|
||||
LARGE_INTEGER win_current_count;
|
||||
::QueryPerformanceCounter(&win_current_count);
|
||||
|
||||
system_milliseconds = (unsigned long)(win_current_count.QuadPart * 1000.0 / win_frequency.QuadPart);
|
||||
|
||||
#else
|
||||
gettimeofday(&time_val,&time_zone);
|
||||
|
||||
system_milliseconds = (unsigned long)time_val.tv_usec;
|
||||
system_milliseconds /= 1000;
|
||||
system_milliseconds += (unsigned long)(time_val.tv_sec*1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
if (paused_state) paused_time += system_milliseconds-last_update;
|
||||
|
||||
time_elapsed = system_milliseconds-start_time-paused_time+offset;
|
||||
}
|
||||
|
||||
|
||||
unsigned long Timer::getMilliseconds() const
|
||||
{
|
||||
return time_elapsed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
double Timer::getSeconds() const
|
||||
{
|
||||
return ((double)getMilliseconds())/1000.0;
|
||||
}
|
||||
|
||||
|
||||
void Timer::setMilliseconds(unsigned long milliseconds_in)
|
||||
{
|
||||
offset -= (system_milliseconds-start_time-paused_time+offset)-milliseconds_in;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Timer::setSeconds(double seconds_in)
|
||||
{
|
||||
setMilliseconds((long)(seconds_in*1000.0));
|
||||
}
|
||||
|
||||
|
||||
double Timer::lastUpdateSeconds() const
|
||||
{
|
||||
return ((double)lastUpdateMilliseconds())/1000.0;
|
||||
}
|
||||
|
||||
|
||||
unsigned long Timer::lastUpdateMilliseconds() const
|
||||
{
|
||||
return system_milliseconds-last_update;
|
||||
}
|
||||
|
||||
unsigned long Timer::totalMilliseconds() const
|
||||
{
|
||||
return system_milliseconds-start_time;
|
||||
}
|
||||
|
||||
|
||||
double Timer::totalSeconds() const
|
||||
{
|
||||
return totalMilliseconds()/1000.0;
|
||||
}
|
||||
|
||||
|
||||
unsigned long Timer::getNumUpdates() const
|
||||
{
|
||||
return num_updates;
|
||||
}
|
||||
|
||||
|
||||
void Timer::paused(bool pause_in)
|
||||
{
|
||||
paused_state = pause_in;
|
||||
}
|
||||
|
||||
bool Timer::paused() const
|
||||
{
|
||||
return paused_state;
|
||||
}
|
||||
|
||||
void Timer::timerTestFunc() {
|
||||
update();
|
||||
if (getNumUpdates() % 120 == 0) {
|
||||
std::cout << getNumUpdates() << "," << getSeconds() << " Rate: " << ((double)getNumUpdates()/getSeconds()) << "/sec" << std::endl;
|
||||
}
|
||||
if (getNumUpdates() >= 600) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
171
Software/CubicSDR/src/util/Timer.h
Normal file
171
Software/CubicSDR/src/util/Timer.h
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#ifndef TIMER_H
|
||||
#define TIMER_H
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
/// Timer Class, high resolution timer
|
||||
/**
|
||||
* Class provides high resolution timing and useful time control functions
|
||||
*/
|
||||
|
||||
class Timer {
|
||||
private:
|
||||
|
||||
unsigned long time_elapsed;
|
||||
unsigned long system_milliseconds;
|
||||
unsigned long start_time;
|
||||
unsigned long end_time;
|
||||
unsigned long last_update;
|
||||
unsigned long num_updates;
|
||||
unsigned long paused_time;
|
||||
unsigned long offset;
|
||||
|
||||
#ifndef _WIN32
|
||||
struct timeval time_val;
|
||||
struct timezone time_zone;
|
||||
#else
|
||||
LARGE_INTEGER win_frequency;
|
||||
#endif
|
||||
|
||||
bool paused_state;
|
||||
bool lock_state;
|
||||
float lock_rate;
|
||||
|
||||
public:
|
||||
|
||||
/// Constructor
|
||||
Timer();
|
||||
|
||||
/// Start the timer
|
||||
/**
|
||||
* Resets the timer to 0 and begins timing
|
||||
*/
|
||||
void start();
|
||||
|
||||
/// Stop the timer
|
||||
/**
|
||||
* Stops the timer and records the end time
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/// Locks the timer to a specified framerate (for recording / benchmarking purposes typically)
|
||||
/**
|
||||
* Locks the timer to a specified framerate (for recording / benchmarking purposes typically)
|
||||
*/
|
||||
void lockFramerate(float f_rate);
|
||||
|
||||
/// Unlock any framerate lock that's been applied
|
||||
/**
|
||||
* Unlock any framerate lock that's been applied
|
||||
*/
|
||||
void unlock();
|
||||
|
||||
/// Check locked state
|
||||
/**
|
||||
* Check locked state
|
||||
*/
|
||||
bool locked() const;
|
||||
|
||||
/// Reset the timer counter
|
||||
/**
|
||||
* Resetting the timer will reset the current time to 0
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/// Timer update
|
||||
/**
|
||||
* Calling the update command will bring the timer value up to date, this is meant
|
||||
* to be called at the beginning of the frame to establish the time index which is being drawn.
|
||||
*/
|
||||
void update();
|
||||
|
||||
/// Get the total time elapsed since the timer start, not counting paused time
|
||||
/**
|
||||
* Returns the total time elapsed in since the timer start() to the last update() but
|
||||
* does not count the time elapsed while the timer is paused().
|
||||
* \return Total time elapsed since the timer start() to the last update() excluding time paused() in milliseconds
|
||||
*/
|
||||
unsigned long getMilliseconds() const;
|
||||
|
||||
/// Alias of getMilliseconds() which returns time in seconds
|
||||
/**
|
||||
* \return Total time elapsed since the timer start() to the last update() excluding time paused() in seconds
|
||||
*/
|
||||
double getSeconds() const;
|
||||
|
||||
/// Get the total time elapsed since the timer start
|
||||
/**
|
||||
* Returns the total time elapsed in since the timer start() to the last update()
|
||||
* this includes any time accumulated during updates while paused()
|
||||
* \return Total time elapsed since the timer start() to the last update() including time paused() in milliseconds
|
||||
*/
|
||||
unsigned long totalMilliseconds() const;
|
||||
/// Alias of totalMilliseconds() which returns time in seconds
|
||||
/**
|
||||
* \return Total time elapsed since the timer start() to the last update() including time paused() in seconds
|
||||
*/
|
||||
double totalSeconds() const;
|
||||
|
||||
/// Set the amount of time elapsed
|
||||
/**
|
||||
* Force the timer duration to a specific value, useful for rolling forward or back in a system
|
||||
* based upon the timer.
|
||||
* \param milliseconds_in Time to set timer to in milliseconds
|
||||
*/
|
||||
void setMilliseconds(unsigned long milliseconds_in);
|
||||
/// alias of setMilliseconds() which accepts time in seconds
|
||||
/**
|
||||
* \param seconds_in Time to set timer to in seconds
|
||||
*/
|
||||
void setSeconds(double seconds_in);
|
||||
|
||||
/// Get the amount of times the update() command has been called
|
||||
/**
|
||||
* By using the number of times the update() command has been called you can easily determine
|
||||
* an average frame rate. Also useful for merely determining how many frames have been drawn.
|
||||
* \return Number of times update() has been called
|
||||
*/
|
||||
unsigned long getNumUpdates() const;
|
||||
|
||||
/// Get the timer duration during the last update
|
||||
/**
|
||||
* Useful for determining the amount of time which elapsed during the last update
|
||||
* can be used to accurately control values with a per-second rate or determine the current frame rate.
|
||||
* \return Duration of time between the last two calls to update() in milliseconds
|
||||
*/
|
||||
unsigned long lastUpdateMilliseconds() const;
|
||||
/// Alias of lastUpdateMilliseconds() which returns time in seconds
|
||||
/**
|
||||
* \return Duration of time between the last two calls to update() in seconds
|
||||
*/
|
||||
double lastUpdateSeconds() const;
|
||||
|
||||
/// Set the timer pause state
|
||||
/**
|
||||
* Pause the timer, allowing for continued update() calls without an increment in timing but
|
||||
* maintaining the update and total time count, useful for pausing a scene but allowing frame
|
||||
* timing to resume.
|
||||
* \param pause_in Value to set the current pause state to
|
||||
*/
|
||||
void paused(bool pause_in);
|
||||
|
||||
/// Check if the timer is currently in a paused state
|
||||
/**
|
||||
* \return Current pause state, true if paused, false otherwise
|
||||
*/
|
||||
bool paused() const;
|
||||
|
||||
|
||||
|
||||
void timerTestFunc();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user