C++ 如何在C++; 背景
我正在使用许多不同的显示器(硬件)和不同的画布(这是画布的复数形式吗?)。每个都可以使用不同的颜色。示例案例:C++ 如何在C++; 背景,c++,templates,colors,embedded,C++,Templates,Colors,Embedded,我正在使用许多不同的显示器(硬件)和不同的画布(这是画布的复数形式吗?)。每个都可以使用不同的颜色。示例案例: RGB16画布->RGB16显示(这是直截了当的,但我不想只使用异国情调的组合) RGB16画布->RGB24显示 单色画布->RGB16显示,其中“true”必须在运行时显示为颜色集 目前,我确实正在使用mono->rgb16组合(3),以使所有内容都显示为红色且可调光。可能出现的每个显示器的颜色类型也可能略有不同 我想要什么 我想要一组易于扩展的颜色类(C++)。我的目标是能够写简
Monochrome m; // default value set at runtime
RGB556 rgb; // default value set at runtime
rgb = m; // conversion function known at compile time
而且
pixelBuffer<Monochrome,w*h> src;
pixelBuffer<RGB556,w*h> dest;
std::copy(src.begin(), src.end(), dest.begin());
以及输出:
Abstract Interface:
time = 1241 us
rgb[N/2].r() = 0x00
copies: 1000
Direct copy:
time = 157 us
rgb[N/2].r() = 0x00
copies: 2000
当我外推第一个数字1241微秒时,对于每秒25帧的128*128显示,仅在颜色之间转换就需要50%的CPU时间。计算结果为:128*128*25像素每秒*1241 us/1000像素=0.51秒
。为这种情况编写的画布/驱动程序组合可以在大约0.1秒内完成,而且它确实必须每秒复制和转换这么多像素,因为整个显示在每一帧中绘制
这种比较可能有点不公平,但请容忍我。我在分析方面不是很有经验;而编写代码来公平比较我所拥有的和我想要拥有的是不可能的。关键是单色本质上只是一个bool,当我有合适的代码时,编译器应该能够对此进行优化
实验2:使用模板转换函数分离类 正如安德烈所建议的,我编写了一个RGB24类,将其定义为
MostPreciseFormat
,以及一个模板化的免费convert
函数。也就是说,我不确定这是否正是他的意思:
class RGB24
{
public:
RGB24() : r_(0), g_(0), b_(0) {}
uint8_t r_, g_, b_;
uint8_t r() const {return r_;}
uint8_t g() const {return g_;}
uint8_t b() const {return b_;}
void setR(const uint8_t& r) {r_ = r;}
void setG(const uint8_t& g) {g_ = g;}
void setB(const uint8_t& b) {b_ = b;}
template<typename Other>
RGB24(const Other& other)
{
convert(*this, other);
}
template <typename Other>
RGB24& operator=(const Other& other)
{
convert(*this, other);
return *this;
}
};
typedef RGB24 MostPreciseFormat;
template <typename To, typename From>
void convert (To& to, const From& from)
{
// Serial.println("Convert() called"); Serial.flush();
MostPreciseFormat precise;
precise.setR(from.r());
precise.setG(from.g());
precise.setB(from.b());
to = precise;
}
template <>
void convert(RGB24& to, const bool& from)
{
if (from)
{
to.setR(0xFF);
to.setG(0xFF);
to.setB(0xFF);
}
else
{
to.setR(0);
to.setG(0);
to.setB(0);
}
}
RGB24类
{
公众:
RGB24():r_0,g_0,b_0{}
uint8_t r_,g_,b_;
uint8_t r()常量{return r_;}
uint8_t g()常量{return g_;}
uint8_t b()常量{return b_;}
void setR(const uint8_t&r){r_=r;}
void setG(const uint8_t&g){g_=g;}
void setB(const uint8_t&b){b_=b;}
模板
RGB24(常数其他和其他)
{
转换(*本,其他);
}
模板
RGB24和运算符=(常量其他和其他)
{
转换(*本,其他);
归还*这个;
}
};
typedef RGB24最精确的格式;
模板
无效转换(到和到,常量从和从)
{
//Serial.println(“Convert()调用”);Serial.flush();
最精确的格式;
precise.setR(from.r());
precise.setG(from.g());
精确的.setB(from.b());
to=精确;
}
模板
无效转换(RGB24和to、常量布尔和from)
{
如果(从)
{
to.setR(0xFF);
to.setG(0xFF);
to.setB(0xFF);
}
其他的
{
至.setR(0);
至.setG(0);
至0.setB(0);
}
}
1000像素的转换需要209微秒,这似乎是合理的。但我做对了吗
我现在有什么 根据安德烈的回答,这是预期的结果。它有一些问题,可能需要在这里和那里进行一些重组。我还没有查看它所需的CPU时间:
#include <bitset>
#include <iostream>
#include <stdint.h>
using namespace std;
namespace channel
{
static constexpr struct left_aligned_t {} left_aligned = left_aligned_t();
static constexpr struct right_aligned_t {} right_aligned = right_aligned_t();
template<typename T, unsigned int Offset_, unsigned int Width_>
class Proxy
{
public:
/* Some checks and typedefs */
static_assert(std::is_unsigned<T>::value, "ChannelProxy: T must be an unsigned arithmetic type.");
typedef T data_type;
static constexpr unsigned int Width = Width_;
static_assert(Width <= 8, "ChannelProxy: Width must be <= 8.");
static constexpr unsigned int Offset = Offset_;
static_assert((Offset + Width) <= 8*sizeof(T), "ChannelProxy: Channel is out of the data type's bounds. Check data type, offset and width.");
Proxy(T& data) : data_(data) {}
uint8_t read(right_aligned_t) const
{
return ((data_ & read_mask) >> Offset);
}
uint8_t read(left_aligned_t) const
{
return read(right_aligned) << (8-Width);
}
void write(const uint8_t& value, right_aligned_t)
{
// input data is right aligned
data_ = (data_ & write_mask) | ((value & value_mask) << Offset);
}
void write(const uint8_t& value, left_aligned_t)
{
// input data is left aligned, so shift right to right align, then write
write(value >> (8-Width), right_aligned);
}
private:
static constexpr uint8_t value_mask = (uint8_t)((1<<Width)-1);
static constexpr T read_mask = (value_mask << Offset);
static constexpr T write_mask = (T)~read_mask;
T& data_;
};
} // namespace channel
struct RGB24
{
typedef channel::Proxy<uint8_t, 0, 8> proxy;
typedef channel::Proxy<const uint8_t, 0, 8> const_proxy;
RGB24() : r_(0), g_(0), b_(0) {}
RGB24(const uint8_t& r, const uint8_t& g, const uint8_t& b)
: r_(r), g_(g), b_(b) {}
// unfortunately, we need different proxies for read and write access (data_type constness)
const_proxy r() const {return const_proxy(r_);}
proxy r() {return proxy(r_);}
const_proxy g() const {return const_proxy(g_);}
proxy g() {return proxy(g_);}
const_proxy b() const {return const_proxy(b_);}
proxy b() {return proxy(b_);}
template <typename From>
RGB24& operator=(const From& from)
{
convert(*this, from);
return *this;
}
uint8_t r_;
uint8_t g_;
uint8_t b_;
};
struct RGB565 // 16 bits: MSB | RRRRR GGGGGG BBBBB | LSB
{
typedef uint16_t data_type;
typedef channel::Proxy<data_type, 0, 5> b_proxy;
typedef channel::Proxy<const data_type, 0, 5> const_b_proxy;
typedef channel::Proxy<data_type, 5, 6> g_proxy;
typedef channel::Proxy<const data_type, 5, 6> const_g_proxy;
typedef channel::Proxy<data_type, 11, 5> r_proxy;
typedef channel::Proxy<const data_type, 11, 5> const_r_proxy;
RGB565() : data_(0) {}
template <typename alignment_type = channel::right_aligned_t>
RGB565(const uint8_t& r_, const uint8_t& g_, const uint8_t& b_, alignment_type = alignment_type())
{
alignment_type alignment;
r().write(r_, alignment);
g().write(g_, alignment);
b().write(b_, alignment);
}
template <typename From>
RGB565& operator=(const From& from)
{
convert(*this, from);
return *this;
}
const_r_proxy r() const {return const_r_proxy(data_);}
r_proxy r() {return r_proxy(data_);}
const_g_proxy g() const {return const_g_proxy(data_);}
g_proxy g() {return g_proxy(data_);}
const_b_proxy b() const {return const_b_proxy(data_);}
b_proxy b() {return b_proxy(data_);}
data_type data_;
};
typedef bool Monochrome;
template <typename To, typename From>
void convert(To& to, const From& from)
{
to.r().write(from.r().read(channel::left_aligned), channel::left_aligned);
to.g().write(from.g().read(channel::left_aligned), channel::left_aligned);
to.b().write(from.b().read(channel::left_aligned), channel::left_aligned);
}
/* bool to RGB565 wouldn't work without this: */
template <>
void convert<RGB565, Monochrome>(RGB565& to, const Monochrome& from)
{
to.data_ = from ? 0xFFFF : 0;
}
int main()
{
cout << "Initializing RGB24 color0(0b11111101, 0, 0)\n\n";
RGB24 color0(0b11111101, 0, 0);
cout << "Initializing RGB24 color1(default)\n\n";
RGB24 color1;
cout << "color 1 = color0\n";
color1 = color0;
cout << "color1.r() = " << std::bitset<8*sizeof(uint8_t)>(color1.r().read(channel::right_aligned)) << "\n";
cout << "color1.g() = " << std::bitset<8*sizeof(uint8_t)>(color1.g().read(channel::right_aligned)) << "\n";
cout << "color1.b() = " << std::bitset<8*sizeof(uint8_t)>(color1.b().read(channel::right_aligned)) << "\n\n";
cout << "Initializing RGB565 color2(0b10001, 0b100100, 0b10100)\n";
RGB565 color2(0b10001, 0b100100, 0b10100);
cout << "color2.data = " << std::bitset<8*sizeof(uint16_t)>(color2.data_) << "\n";
cout << "color2.b(right aligned) = " << std::bitset<8*sizeof(uint8_t)>(color2.b().read(channel::right_aligned)) << "\n";
cout << "color2.b(left aligned) = " << std::bitset<8*sizeof(uint8_t)>(color2.b().read(channel::left_aligned)) << "\n\n";
cout << "color 0 = color2\n";
color0 = color2;
cout << "color0.b(right aligned) = " << std::bitset<8*sizeof(uint8_t)>(color0.b().read(channel::right_aligned)) << "\n";
cout << "color0.b(left aligned) = " << std::bitset<8*sizeof(uint8_t)>(color0.b().read(channel::left_aligned)) << "\n\n";
cout << "Initializing Monochrome color3(true)\n\n";
Monochrome color3 = true;
cout << "color 2 = color3\n";
color2 = color3;
cout << "color2.data = " << std::bitset<8*sizeof(uint16_t)>(color2.data_) << "\n";
cout << "color2.b(right aligned) = " << std::bitset<8*sizeof(uint8_t)>(color2.b().read(channel::right_aligned)) << "\n";
cout << "color2.b(left aligned) = " << std::bitset<8*sizeof(uint8_t)>(color2.b().read(channel::left_aligned)) << "\n\n";
return 0;
}
#包括
#包括
#包括
使用名称空间std;
名称空间通道
{
静态constexpr struct left_aligned_t{}left_aligned=left_aligned_t();
静态constexpr struct right_aligned_t{}right_aligned=right_aligned_t();
模板
类代理
{
公众:
/*一些支票和打字机*/
static_assert(std::is_unsigned::value,“ChannelProxy:T必须是无符号算术类型”);
typedef T数据类型;
static constexpr unsigned int Width=Width;
静态(宽度偏移);
}
uint8读取(左对齐)常数
{
返回读取(右对齐)(8宽),右对齐);
}
私人:
静态constexpr uint8\u t value\u mask=(uint8\u t)((1使用setter/getter方法创建颜色界面
class Color {
public:
void setR(unsigned char )=0;
unsigned char getR()=0;
...
}
并根据需要使用位字段数据和重写的方法将其继承到自定义颜色类
class RGB556 : public Color {
unsigned char r:5;
unsigned char g:5;
unsigned char b:6;
public:
void setR(unsigned char r) { this->r=r; }
unsigned char getR() { return r; }
...
}
首先,从问题来看,我认为解决方案需要非常快的执行时间,因此这一要求应该推动我们尝试解决问题的方法
这导致我们的结论是,我们应该避免在每像素的基础上调用虚拟函数,否则CPU将不得不为每个像素进行一个额外的不必要的间接寻址。然而,我们根本不应该避免虚拟函数,因为在每画布操作上使用它们是完全可以接受的
因此,我建议的一般解决方案是关注画布类的运行时灵活性,例如,您可以对每个画布类型使用继承,并关注像素操作的编译时绑定
这个问题表明,颜色类要解决的最重要的功能是它们之间的颜色格式转换,因此我现在将重点介绍这一点。您可以使用三种方法实现类型之间的转换函数:
- 星形:每种颜色格式都可以转换为最精确的格式,也可以转换为最精确的格式。要将A转换为B,首先将A转换为最精确的格式,然后再从该格式转换为B。这很简单,而且扩展性很强,因为添加一种新格式只需要定义另外两个函数,即函数的数量与函数的数量呈线性增长格式(O(N))
- 完全连接:每种颜色都可以与其他颜色格式进行转换。这要快得多,因为只需要一次转换,优化的工作量最小,潜力最大。但是,函数的数量是O(N2)
- 混合:如果定义了直接转换,则使用它,否则,使用最精确的格式作为中介
为了让编译器选择正确的转换函数,模板是我能找到的最优雅的解决方案。
class Color {
public:
void setR(unsigned char )=0;
unsigned char getR()=0;
...
}
class RGB556 : public Color {
unsigned char r:5;
unsigned char g:5;
unsigned char b:6;
public:
void setR(unsigned char r) { this->r=r; }
unsigned char getR() { return r; }
...
}
// Complete and repeat the class definition below for every color format.
// There is no specific interface to follow, but all classes must have a
// template constructor and a template assignment operator to convert from
// other color formats.
class ColorXYZ {
public:
...
template <class Other>
ColorXYZ(const Other& other) {
convert(*this, other);
}
template <class Other>
ColorXYZ& operator=(const Other& other) {
convert(*this, other);
return *this;
}
...
};
// These should be class definitions, not just forward declarations:
class ColorMono;
class ColorRGB16;
class ColorRGB24;
// Every format must be able to convert to and from the MostPreciseFormat
typedef ColorRGB24 MostPreciseFormat;
// Generic conversion of color formats that converts to MostPreciseFormat and
// then to the required format.
template <class To, class From>
void convert(To& to, const From& from) {
MostPreciseFormat precise(from);
convert(to, precise);
}
// Specialization to convert from Mono to RGB24.
template <>
void convert<ColorRGB24, ColorMono>(ColorRGB24& to, const ColorMono& from) {
// specific code to convert from mono to RGB24.
to.setR(from.value() ? 255 : 0);
to.setG(from.value() ? 255 : 0);
to.setB(from.value() ? 255 : 0);
}
... // A lot of other specializations of convert here.
convert<ColorRGB24, ColorMono>
convert<ColorRGB24, ColorRGB16>
convert<ColorRGB24, ColorRGB24>
convert<ColorRGB24, ColorXYZ>
convert<ColorMono, ColorRGB24>
convert<ColorRGB16, ColorRGB24>
convert<ColorRGB24, ColorRGB24>
convert<ColorXYZ, ColorRGB24>
class Canvas {
public:
...
virtual void setPixel(unsigned int index, ColorRGB24 color) = 0;
...
};
class CanvasMono {
public:
...
virtual void setPixel(unsigned int index, ColorRGB24 color) {
pixel[index] = color; // converting from RGB24 to mono
}
...
private:
ColorMono* pixel;
};