Disclaimer: I can't easily make the code in "What I have tried" line wrap on a display this narrow fixed layout. It might help to view it in fluid mode?
I've got a problem where I'm lost in rectangles. The issue (at least unless I'm missing something else as well) seems to be I just have subpar spatial reasoning abilities and I can't offset my rectangles correctly.
Here's what I am doing. I have a "screen" with a background color and a number of "controls".
I have a small fixed buffer (usually 32kB or less) that can be rendered at a time. For RGB565 16-bit color this is 2 bytes per pixel, while a screen might be, say 480x320.
Each control has a
valid()
flag that indicates whether it needs to be re-rendered, while
visible()
indicates whether it should be drawn at all.
When each control is rendered, its logical coordinates are upper left (0,0), so I have a
control_surface
wrapper that offsets all draws to an underlying bitmap it holds.
First I find dirty rectangles. This is easy and works, though I'm sure it could be optimized.
Then for each dirty rectangle, I create a bitmap over the buffer that's the width of the rectangle and however many lines in height I can hold in the memory I have to work with. I also create a subrectangle
sr
that represents that subset of the dirty rectangle. This is because my maximum usable memory buffer may not be large enough to permit me to render all of the dirty rectangle at once. In this case it moves from top to bottom of the dirty rect, rendering that portion of the rect.
sr
represents the subset of the dirty rectangle being rendered. Note that in cases where a control overlaps multiple subrects, it will be rendered more than once.
Then for each subrectangle, I go through any controls that are
visible()
and whose
bounds()
intersect it.
I then try to create the logical offset for the control, and this is where I can't seem to get it right. I can get the topmost/first render iteration to work but successive renders do not. Even as I offset the logical coordinates of the control surface as
y
increases it still doesn't want to work for me at all. My offsets are always wrong.
I didn't put everything I've tried in the tried section, because I've been at this for a day and a half. Instead the code for the primary render routine is there. I'm available to answer questions about it because I know it's a lot to take in. As far as I can tell most of it works. The
// TODO:
is the part I'm struggling with.
What I have tried:
uix_result render(render_callback_type render_callback,void* state = nullptr, size_t max_memory = 32*1024, uint8_t* render_buffer = nullptr, void*(allocator)(size_t size)=::malloc,void(deallocator)(void*)=::free) {
data::simple_vector<rect16> dirties;
if(!m_valid) {
dirties.push_back(bounds());
} else {
for(control_type** it = m_controls.begin();it!=m_controls.end();++it) {
const control_type *pit = *it;
if(!pit->valid() && pit->visible()) {
rect16 r = (rect16)pit->bounds().crop((srect16)this->bounds());
rect16* pr = &r;
if(is_already_contained(dirties,pr)) {
if(pr!=nullptr) {
pr->x1 = r.x1;
pr->y1 = r.y1;
pr->x2 = r.x2;
pr->y2 = r.y2;
}
} else {
dirties.push_back(r);
}
}
}
}
if(dirties.size()==0) {
return uix_result::success;
}
Serial.printf("Found %d dirty rects.\n",(int)dirties.size());
uint32_t max_area = 0;
rect16 largest_rect;
for(const rect16* it = dirties.cbegin();it!=dirties.cend();++it) {
uint32_t area = it->width()*it->height();
if(area>max_area) {
max_area = area;
largest_rect = *it;
}
}
size_t bmp_size = bitmap_type::sizeof_buffer(largest_rect.dimensions());
size_t bmp_stride_size = bitmap_type::sizeof_buffer({largest_rect.width(),1});
if(max_memory<bmp_stride_size) {
return uix_result::invalid_argument;
}
if(bmp_size>max_memory) {
bmp_size = bitmap_type::sizeof_buffer({largest_rect.width(),uint16_t(max_memory/bmp_stride_size)});
}
uint8_t* bmp_data = m_render_buffer!=nullptr?m_render_buffer:(uint8_t*)allocator(bmp_size);
while(bmp_data==nullptr && bmp_size>bmp_stride_size) {
bmp_size/=2;
bmp_data = (uint8_t*)allocator(bmp_size);
}
if(bmp_data==nullptr) {
return uix_result::out_of_memory;
}
Serial.println("Allocated bitmap render buffer");
for(const rect16* it = dirties.cbegin();it!=dirties.cend();++it) {
rect16 r = *it;
Serial.printf("Dirty rect is (%d,%d)-(%d,%d)\n",(int)r.x1,(int)r.y1,(int)r.x2,(int)r.y2);
size_t r_size = bitmap_type::sizeof_buffer(r.dimensions());
size_t lines = bmp_size/bitmap_type::sizeof_buffer({r.width(),1});
Serial.printf("Allocated %d lines.\n",(int)lines);
bitmap_type bmp({r.width(),(uint16_t)lines},bmp_data);
bmp.fill(bmp.bounds(),m_background_color);
int h = (int)r.height();
for(int y = 0;y<h;y+=lines) {
srect16 sr(r.x1,r.y1+y,r.x2,r.y1+lines+y);
Serial.printf("sr subrect is (%d,%d)-(%d,%d)\n",(int)sr.x1,(int)sr.y1,(int)sr.x2,(int)sr.y2);
bool found = false;
for(control_type** cit = m_controls.begin();cit!=m_controls.end();++cit) {
control_type* pcit = *cit;
srect16 r_offset = pcit->bounds();
if(pcit->visible() && r_offset.intersects(sr)) {
found = true;
r_offset = r_offset.crop(sr);
r_offset.offset_inplace(-sr.x1,-sr.y1);
Serial.printf("Offset rect is (%d,%d)-(%d,%d)\n",(int)r_offset.x1,(int)r_offset.y1,(int)r_offset.x2,(int)r_offset.y2);
control_surface_type surface(bmp,r_offset);
pcit->on_render(surface);
}
}
render_callback({r.x1,uint16_t(r.y1+y)},bmp,state);
if(y<h && found) {
bmp.fill(bmp.bounds(),m_background_color);
}
}
}
if(m_render_buffer!=nullptr) {
deallocator(bmp_data);
}
m_valid = true;
return uix_result::success;
}