diff --git a/apps/graph/graph/cursor_view.cpp b/apps/graph/graph/cursor_view.cpp index e3174a32f..f635c3185 100644 --- a/apps/graph/graph/cursor_view.cpp +++ b/apps/graph/graph/cursor_view.cpp @@ -1,6 +1,5 @@ #include "cursor_view.h" void CursorView::drawRect(KDRect rect) const { - KDColor cursorColor = KDColorRed; - KDFillRect(rect, &cursorColor, 1); + KDFillRect(rect, KDColorRed); } diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 3ea307193..6a39f5464 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -8,7 +8,11 @@ constexpr int kNumberOfMainGridLines = 5; constexpr int kNumberOfSecondaryGridLines = 4; GraphView::GraphView() : +#if GRAPH_VIEW_IS_TILED + TiledView(), +#else View(), +#endif m_cursorView(CursorView()), m_cursorPosition(KDPointZero), m_xMin(-2.0f), @@ -39,36 +43,65 @@ void GraphView::layoutSubviews() { m_cursorView.setFrame(cursorFrame); } +#if GRAPH_VIEW_IS_TILED +KDColor * GraphView::tile() const { + return (KDColor *)m_tile; +} + +KDSize GraphView::tileSize() const { + return {kTileWidth, kTileHeight}; +} + +void GraphView::drawTile(KDRect rect) const { +#else void GraphView::drawRect(KDRect rect) const { - KDColor backgroundColor = KDColorWhite; - KDFillRect(rect, &backgroundColor, 1); +#endif + KDFillRect(rect, KDColorWhite); drawGrid(rect); drawAxes(rect); drawFunction(rect); + /* + + constexpr int maskLength = 3; + uint8_t mask[maskLength] = { +#if 1 + 0x10, 0x70, 0xE0, +#else + 0x00, 0x30, 0x73, 0x30, 0x00, + 0x30, 0xfb, 0xff, 0xfb, 0x30, + 0x73, 0xff, 0xff, 0xff, 0x73, + 0x30, 0xfb, 0xff, 0xfb, 0x30, + 0x00, 0x30, 0x73, 0x30, 0x00 +#endif + }; + + KDColor red = KDColorRed; + KDBlitRect(rect, &red, 1, mask, maskLength); + */ } -void GraphView::drawLine(KDRect rect, Axis axis, float coordinate, KDColor color) const { +void GraphView::drawLine(KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness) const { KDRect lineRect; switch(axis) { case Axis::Horizontal: lineRect.x = rect.x; lineRect.y = floatToPixel(Axis::Vertical, coordinate); lineRect.width = rect.width; - lineRect.height = 1; + lineRect.height = thickness; break; case Axis::Vertical: lineRect.x = floatToPixel(Axis::Horizontal, coordinate); lineRect.y = rect.y; - lineRect.width = 1; + lineRect.width = thickness; lineRect.height = rect.height; break; } - KDFillRect(lineRect, &color, 1); + KDFillRect(lineRect, color); } void GraphView::drawAxes(KDRect rect) const { - drawLine(rect, Axis::Horizontal, 0.0f, kAxisColor); - drawLine(rect, Axis::Vertical, 0.0f, kAxisColor); + drawLine(rect, Axis::Horizontal, 0.0f, kAxisColor, 2); + drawLine(rect, Axis::Vertical, 0.0f, kAxisColor, 2); } void GraphView::drawGridLines(KDRect rect, Axis axis, int count, KDColor color) const { @@ -113,11 +146,80 @@ KDCoordinate GraphView::floatToPixel(Axis axis, float f) const { } void GraphView::drawFunction(KDRect rect) const { - KDPoint p = KDPointZero; - for (p.x=rect.x; p.x<(rect.x+rect.width); p.x++) { + KDPoint p; + + constexpr KDCoordinate stampSize = 5; + + uint8_t mask[stampSize*stampSize] = { + 0x00, 0x30, 0x73, 0x30, 0x00, + 0x30, 0xfb, 0xff, 0xfb, 0x30, + 0x73, 0xff, 0xff, 0xff, 0x73, + 0x30, 0xfb, 0xff, 0xfb, 0x30, + 0x00, 0x30, 0x73, 0x30, 0x00 + }; + + KDColor workingBuffer[stampSize*stampSize]; + + for (p.x=rect.x-stampSize; p.x<(rect.x+rect.width); p.x++) { float x = pixelToFloat(Axis::Horizontal, p.x); float y = (x-1)*(x+1)*x; p.y = floatToPixel(Axis::Vertical, y); - KDSetPixel(p, KDColorRGB(0xCF, 0, 0)); + KDRect stampRect; + stampRect.origin = p; + stampRect.width = stampSize; + stampRect.height = stampSize; + //KDColor red = KDColorRed; + KDFillRectWithMask(stampRect, KDColorRed, mask, workingBuffer); + //KDBlitRect(stampRect, &red, {1,1}, mask, {stampSize,stampSize}); } } + +#if 0 + +void GraphView::drawFunction(KDRect rect) const { + /* Naive algorithm: iterate on pixels horizontally + * Actually, this is kinda optimal. If two consecutive pixels don't touch + * (i.e. they have a y-difference greater than 1), just draw a vertical line. + * This may seem stupid but is kinda good actually. */ + KDCoordinate previousPixelY; + KDCoordinate pixelX; + + KDColor curveColor = KDColorRGB(0xFF, 0, 0); +#define ANTI_ALIASING +#ifdef ANTI_ALIASING + KDColor antiAliasingColor = KDColorRGB(0, 0xFF, 0); +#endif + + for (pixelX=rect.x; pixelX<(rect.x+rect.width); pixelX++) { + float x = pixelToFloat(Axis::Horizontal, pixelX); + float y = (x-1)*(x+1)*x; + KDCoordinate pixelY = floatToPixel(Axis::Vertical, y); + KDRect r; + r.x = pixelX; + if (pixelY < previousPixelY) { + r.y = pixelY; + r.height = previousPixelY-pixelY; + KDPoint p; + p.x = pixelX-1; + p.y = previousPixelY+3; + KDSetPixel(p, KDColorRGB(0x00, 0xFF, 0x00)); + p.x = pixelX; + p.y = pixelY-1; + KDSetPixel(p, KDColorRGB(0x00, 0xFF, 0x00)); + } else { + r.y = previousPixelY; + r.height = pixelY - previousPixelY; + } + if (r.height == 0) { + r.height = 1; + } + r.width = 1; + + r.width += 2; + r.height += 2; + //KDFillRectWithPattern(r, antialiasedDot); + KDFillRect(r, KDColorRGB(0xFF, 0, 0)); + previousPixelY = pixelY; + } +} +#endif diff --git a/apps/graph/graph/graph_view.h b/apps/graph/graph/graph_view.h index c82ea4886..cf251f237 100644 --- a/apps/graph/graph/graph_view.h +++ b/apps/graph/graph/graph_view.h @@ -4,10 +4,27 @@ #include #include "cursor_view.h" -class GraphView : public View { +#define GRAPH_VIEW_IS_TILED 1 + +class GraphView : public +#if GRAPH_VIEW_IS_TILED + TiledView +#else + View +#endif +{ public: GraphView(); + +#if GRAPH_VIEW_IS_TILED + KDColor * tile() const override; + KDSize tileSize() const override; + void drawTile(KDRect rect) const override; +#else void drawRect(KDRect rect) const override; +#endif + +// void drawRect(KDRect rect) const override; void moveCursorRight(); private: int numberOfSubviews() const override; @@ -27,13 +44,17 @@ private: KDCoordinate floatToPixel(Axis axis, float f) const; void drawLine(KDRect rect, Axis axis, - float coordinate, KDColor color) const; + float coordinate, KDColor color, KDCoordinate thickness = 1) const; void drawAxes(KDRect rect) const; void drawGrid(KDRect rect) const; void drawGridLines(KDRect rect, Axis axis, int count, KDColor color) const; void drawFunction(KDRect rect) const; + static constexpr KDCoordinate kTileWidth = 32; + static constexpr KDCoordinate kTileHeight = 32; + KDColor m_tile[kTileWidth*kTileHeight]; + CursorView m_cursorView; KDPoint m_cursorPosition; diff --git a/apps/graph/list/function_cell.cpp b/apps/graph/list/function_cell.cpp index f9f19e28b..a602ba137 100644 --- a/apps/graph/list/function_cell.cpp +++ b/apps/graph/list/function_cell.cpp @@ -10,7 +10,7 @@ FunctionCell::FunctionCell() : void FunctionCell::drawRect(KDRect rect) const { KDColor background = m_even ? KDColorRGB(0xEE, 0xEE, 0xEE) : KDColorRGB(0x77,0x77,0x77); - KDFillRect(rect, &background, 1); + KDFillRect(rect, background); KDDrawString(m_message, KDPointZero, m_focused); } diff --git a/escher/Makefile b/escher/Makefile index fd06d6e5c..069d9e9b3 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -13,6 +13,7 @@ objs += $(addprefix escher/src/,\ table_view.o\ text_field.o\ text_view.o\ + tiled_view.o\ view.o\ view_controller.o\ window.o\ diff --git a/escher/include/escher.h b/escher/include/escher.h index 159c83b31..b97e6f615 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/escher/include/escher/tiled_view.h b/escher/include/escher/tiled_view.h new file mode 100644 index 000000000..046e5cad0 --- /dev/null +++ b/escher/include/escher/tiled_view.h @@ -0,0 +1,16 @@ +#ifndef ESCHER_TILED_VIEW_H +#define ESCHER_TILED_VIEW_H + +#include + +class TiledView : public View { + using View::View; +protected: + void drawRect(KDRect rect) const override; + virtual void drawTile(KDRect rect) const = 0; + + virtual KDColor * tile() const = 0; + virtual KDSize tileSize() const = 0; +}; + +#endif diff --git a/escher/src/scroll_view_indicator.cpp b/escher/src/scroll_view_indicator.cpp index e3df94114..0b693288f 100644 --- a/escher/src/scroll_view_indicator.cpp +++ b/escher/src/scroll_view_indicator.cpp @@ -15,7 +15,7 @@ ScrollViewIndicator::ScrollViewIndicator(ScrollViewIndicator::Direction directio } void ScrollViewIndicator::drawRect(KDRect rect) const { - KDFillRect(bounds(), &k_backgroundColor, 1); + KDFillRect(bounds(), k_backgroundColor); KDRect indicatorFrame; if (m_direction == Direction::Horizontal) { indicatorFrame.x = m_start*m_frame.width; @@ -29,7 +29,7 @@ void ScrollViewIndicator::drawRect(KDRect rect) const { indicatorFrame.width = m_frame.width; indicatorFrame.height = (m_end-m_start)*m_frame.height; } - KDFillRect(indicatorFrame, &k_indicatorColor, 1); + KDFillRect(indicatorFrame, k_indicatorColor); } void ScrollViewIndicator::setStart(float start) { diff --git a/escher/src/solid_color_view.cpp b/escher/src/solid_color_view.cpp index cf8101154..9c0506c9a 100644 --- a/escher/src/solid_color_view.cpp +++ b/escher/src/solid_color_view.cpp @@ -7,7 +7,7 @@ SolidColorView::SolidColorView(KDColor color) : } void SolidColorView::drawRect(KDRect rect) const { - KDFillRect(rect, &m_color, 1); + KDFillRect(rect, m_color); } #if ESCHER_VIEW_LOGGING diff --git a/escher/src/tab_view.cpp b/escher/src/tab_view.cpp index 679c5372c..3b4502358 100644 --- a/escher/src/tab_view.cpp +++ b/escher/src/tab_view.cpp @@ -13,7 +13,7 @@ TabView::TabView() : void TabView::drawRect(KDRect rect) const { KDColor backgroundColor = KDColorRGB(0xb5, 0x1d, 0xab); - KDFillRect(rect, &backgroundColor, 1); + KDFillRect(rect, backgroundColor); } void TabView::addTabNamed(const char * name) { diff --git a/escher/src/tiled_view.cpp b/escher/src/tiled_view.cpp new file mode 100644 index 000000000..b2b341810 --- /dev/null +++ b/escher/src/tiled_view.cpp @@ -0,0 +1,53 @@ +#include +#include + +static KDFrameBuffer sCurrentTile; + +void tilePushRect(KDRect rect, const KDColor * pixels) { + KDFramePushRect(&sCurrentTile, rect, pixels); +} +void tilePushRectUniform(KDRect rect, KDColor color) { + KDFramePushRectUniform(&sCurrentTile, rect, color); +} + +void tilePullRect(KDRect rect, KDColor * pixels) { + KDFramePullRect(&sCurrentTile, rect, pixels); +} + +static KDContext sTileContext = { + { + &tilePushRect, + &tilePushRectUniform, + &tilePullRect + }, + {0, 0}, + {0, 0, 0, 0} +}; + +void TiledView::drawRect(KDRect rect) const { + sCurrentTile.pixels = tile(); + sCurrentTile.size = tileSize(); + + sTileContext.clippingRect.origin = KDPointZero; // Useless + sTileContext.clippingRect.size = sCurrentTile.size; + + KDRect tileRect; + for (int i=0; i<(rect.width/sCurrentTile.size.width+1); i++) { + for (int j=0; j<(rect.height/sCurrentTile.size.height+1); j++) { + tileRect.x = rect.x + i*sCurrentTile.size.width; + tileRect.y = rect.y + j*sCurrentTile.size.height; + tileRect.size = sCurrentTile.size; + //tileRect = KDRectIntersection(tileRect, rect); // Optional + KDContext * previousContext = KDCurrentContext; + sTileContext.origin.x = -tileRect.x; + sTileContext.origin.y = -tileRect.y; + + KDCurrentContext = &sTileContext; + drawTile(tileRect); + KDCurrentContext = previousContext; + KDSize zero = {0,0}; + + KDFillRectWithPixels(tileRect, sCurrentTile.pixels, sCurrentTile.pixels); + } + } +} diff --git a/ion/include/ion/screen.h b/ion/include/ion/screen.h index c00e26029..6ad92ba2d 100644 --- a/ion/include/ion/screen.h +++ b/ion/include/ion/screen.h @@ -11,21 +11,16 @@ * pixel coordinates every time. We're therefore leveraging this capability * which results in a very consequent speedup (up to ~10x faster). */ -#include -#include +#include -/* ION manipulates RGB565 colors */ -typedef uint16_t ion_color_t; - -/* Set the color of a single pixel. */ -void ion_set_pixel(uint16_t x, uint16_t y, ion_color_t color); - -/* Fill a rect with a single color */ -void ion_fill_rect( - uint16_t x, uint16_t y, - uint16_t width, uint16_t height, - ion_color_t * pattern, size_t patternSize -); +void ion_screen_push_rect(KDRect rect, const KDColor * pixels); +void ion_screen_push_rect_uniform(KDRect rect, KDColor color); +void ion_screen_pull_rect(KDRect rect, KDColor * pixels); +/* +void ion_screen_set_working_area(KDRect area); +void ion_screen_push_pixels(const KDColor * pixels, size_t count); +void ion_screen_pull_pixels(KDColor * pixels, size_t count); +*/ #define ION_SCREEN_WIDTH 320 #define ION_SCREEN_HEIGHT 240 diff --git a/ion/src/device/display/display.c b/ion/src/device/display/display.c index a53f6bc2d..d59bf0206 100644 --- a/ion/src/device/display/display.c +++ b/ion/src/device/display/display.c @@ -40,25 +40,59 @@ void ion_display_off() { // Turn off panel } -void ion_set_pixel(uint16_t x, uint16_t y, ion_color_t color) { - st7789_set_drawing_area(&sDisplayController, x, y, 1, 1); - st7789_push_pixels(&sDisplayController, &color, 1); +void ion_screen_push_rect(KDRect rect, const KDColor * pixels) { + st7789_set_drawing_area(&sDisplayController, rect); + st7789_push_pixels(&sDisplayController, pixels, rect.width*rect.height); } -void ion_fill_rect( - uint16_t x, uint16_t y, - uint16_t width, uint16_t height, - ion_color_t * pattern, size_t patternSize) -{ - st7789_set_drawing_area(&sDisplayController, x, y, width, height); - size_t remainingSize = width*height; - while (remainingSize > 0) { - int32_t blockSize = remainingSize > patternSize ? patternSize : remainingSize; - st7789_push_pixels(&sDisplayController, pattern, blockSize); - remainingSize -= blockSize; +void ion_screen_push_rect_uniform(KDRect rect, KDColor color) { + st7789_set_drawing_area(&sDisplayController, rect); + for (size_t i=0; i 0) { + int32_t blockSize = remainingSize > patternSize ? patternSize : remainingSize; + st7789_push_pixels(&sDisplayController, pattern, blockSize); + remainingSize -= blockSize; + } + return; + } +#endif + uint16_t remainingHeight = height; + uint16_t patternLine = 0; + while (remainingHeight-- > 0) { + uint16_t remainingWidth = width; + while (remainingWidth > 0) { + int32_t blockSize = remainingWidth > patternWidth ? patternWidth : remainingWidth; + st7789_push_pixels(&sDisplayController, pattern+patternLine*patternWidth, blockSize); + remainingWidth -= blockSize; + } + if (++patternLine >= patternHeight) { + patternLine = 0; + } + } +} +#endif + void init_display() { //assert(FRAMEBUFFER_LENGTH == (FRAMEBUFFER_WIDTH*FRAMEBUFFER_HEIGHT*FRAMEBUFFER_BITS_PER_PIXEL)/8); diff --git a/ion/src/device/display/st7789.c b/ion/src/device/display/st7789.c index ecf9b50c7..1b1c5d3f5 100644 --- a/ion/src/device/display/st7789.c +++ b/ion/src/device/display/st7789.c @@ -155,11 +155,12 @@ void st7789_initialize(st7789_t * c) { perform_instructions(c, init_sequence, sizeof(init_sequence)/sizeof(init_sequence[0])); } -void st7789_set_drawing_area(st7789_t * controller, int16_t x, int16_t y, int16_t width, int16_t height) { - uint16_t x_start = x; - uint16_t x_end = x + width - 1; - uint16_t y_start = y; - uint16_t y_end = y + height - 1; +void st7789_set_drawing_area(st7789_t * controller, KDRect area) { + assert(sizeof(KDCoordinate) == 2); // We expect 16-bit values + uint16_t x_start = area.x; + uint16_t x_end = area.x + area.width - 1; + uint16_t y_start = area.y; + uint16_t y_end = area.y + area.height - 1; const instruction_t sequence[] = { COMMAND(CASET), @@ -180,8 +181,11 @@ void st7789_set_drawing_area(st7789_t * controller, int16_t x, int16_t y, int16_ perform_instructions(controller, sequence, sizeof(sequence)/sizeof(sequence[0])); } -void st7789_push_pixels(st7789_t * controller, ion_color_t * pixels, int32_t numberOfPixels) { - for (int32_t i=0; i> 8)); perform_instruction(controller, DATA(pixels[i] & 0xFF)); } diff --git a/ion/src/device/display/st7789.h b/ion/src/device/display/st7789.h index 360b451d7..a35ff75ef 100644 --- a/ion/src/device/display/st7789.h +++ b/ion/src/device/display/st7789.h @@ -5,6 +5,7 @@ #include #include #include +#include #define ST7789_USE_9BIT_SPI 1 @@ -22,10 +23,8 @@ typedef struct { void st7789_initialize(st7789_t * controller); -void st7789_set_drawing_area(st7789_t * controller, - int16_t x, int16_t y, - int16_t width, int16_t height); +void st7789_set_drawing_area(st7789_t * controller, KDRect area); void st7789_push_pixels(st7789_t * controller, - ion_color_t * pixels, int32_t numberOfPixels); + const KDColor * pixels, size_t numberOfPixels); #endif diff --git a/ion/src/simulator/display/fltklcd.cpp b/ion/src/simulator/display/fltklcd.cpp index 28e242f85..834f60475 100644 --- a/ion/src/simulator/display/fltklcd.cpp +++ b/ion/src/simulator/display/fltklcd.cpp @@ -2,14 +2,32 @@ #include #include -FltkLCD::FltkLCD(int x, int y, int w, int h, const char * label) : - Fl_Widget(x, y, w, h, label) { - m_framebuffer = malloc(w*h*3); -// FIXME: Delete the framebuffer! +FltkLCD::FltkLCD(int x, int y, int w, int h, uint16_t * rgb565FrameBuffer) : + Fl_Widget(x, y, w, h, nullptr), + m_rgb565frameBufferStart(rgb565FrameBuffer), + m_rgb565frameBufferEnd(rgb565FrameBuffer+w*h) +{ + m_rgb888frameBufferStart = malloc(w*h*3); +} + +FltkLCD::~FltkLCD() { + free(m_rgb888frameBufferStart); } void FltkLCD::draw() { - fl_draw_image((const uchar *)m_framebuffer, + // 1/ Convert the framebuffer from 565 to 888 + KDColor * rgb565Pixel = m_rgb565frameBufferStart; + uint8_t * rgb888Component = (uint8_t *)m_rgb888frameBufferStart; + + while(rgb565Pixel < m_rgb565frameBufferEnd) { + KDColor color = *rgb565Pixel++; + *rgb888Component++ = KDColorRedComponent(color); + *rgb888Component++ = KDColorGreenComponent(color); + *rgb888Component++ = KDColorBlueComponent(color); + } + + // 2/ Draw the 888 framebuffer + fl_draw_image((const uchar *)m_rgb888frameBufferStart, x(), // x y(), // y w(), // width diff --git a/ion/src/simulator/display/fltklcd.h b/ion/src/simulator/display/fltklcd.h index 451c28d4d..f231f5b49 100644 --- a/ion/src/simulator/display/fltklcd.h +++ b/ion/src/simulator/display/fltklcd.h @@ -2,13 +2,18 @@ #define ION_FLTK_LCD #include +#include class FltkLCD : public Fl_Widget { public: - FltkLCD(int x, int y, int w, int h, const char * label = 0); - void * m_framebuffer; + FltkLCD(int x, int y, int w, int h, KDColor * rgb565FrameBuffer); + ~FltkLCD(); protected: void draw(); + private: + KDColor * m_rgb565frameBufferStart; + KDColor * m_rgb565frameBufferEnd; + void * m_rgb888frameBufferStart; }; #endif diff --git a/ion/src/simulator/init.cpp b/ion/src/simulator/init.cpp index 1645b6f5b..38831f836 100644 --- a/ion/src/simulator/init.cpp +++ b/ion/src/simulator/init.cpp @@ -15,6 +15,7 @@ extern "C" { static FltkLCD * sDisplay; static FltkKbd * sKeyboard; +static KDFrameBuffer sFrameBuffer; #define FRAMEBUFFER_ADDRESS (sDisplay->m_framebuffer) @@ -28,34 +29,34 @@ void init_platform() { Fl_Window * window = new Fl_Window(screen_width+2*margin, margin+screen_height+margin+keyboard_height+margin); - sDisplay = new FltkLCD(margin, margin, screen_width, screen_height); + sFrameBuffer.pixels = (KDColor *)malloc(ION_SCREEN_WIDTH*ION_SCREEN_HEIGHT*2); + sFrameBuffer.size.width = ION_SCREEN_WIDTH; + sFrameBuffer.size.height = ION_SCREEN_HEIGHT; + /* + sFrameBuffer.drawingArea.origin = KDPointZero; + sFrameBuffer.drawingArea.size = sFrameBuffer.size; + sFrameBuffer.drawingCursor = KDPointZero; + */ + sDisplay = new FltkLCD(margin, margin, screen_width, screen_height, sFrameBuffer.pixels); sKeyboard = new FltkKbd(margin, margin+screen_height+margin, screen_width, keyboard_height); window->end(); window->show(); - KDCurrentContext->fillRect = NULL; + //KDCurrentContext->fillRect = NULL; } -void ion_set_pixel(uint16_t x, uint16_t y, ion_color_t color) { - assert(x < ION_SCREEN_WIDTH); - assert(y < ION_SCREEN_HEIGHT); - uint8_t * pixel = (uint8_t *)(FRAMEBUFFER_ADDRESS) + 3*((y*ION_SCREEN_WIDTH)+x); - uint8_t red = (color >> 11) << 3; - uint8_t green = ((color >> 5) & 0x3F) << 2; - uint8_t blue = (color & 0x1F) << 3; - *pixel++ = red; - *pixel++ = green; - *pixel++ = blue; +void ion_screen_push_rect(KDRect rect, const KDColor * pixels) { + KDFramePushRect(&sFrameBuffer, rect, pixels); } -void ion_fill_rect( - uint16_t x, uint16_t y, - uint16_t width, uint16_t height, - ion_color_t * pattern, size_t patternSize) -{ - assert(false); +void ion_screen_push_rect_uniform(KDRect rect, KDColor color) { + KDFramePushRectUniform(&sFrameBuffer, rect, color); +} + +void ion_screen_pull_rect(KDRect rect, KDColor * pixels) { + KDFramePullRect(&sFrameBuffer, rect, pixels); } bool ion_key_down(ion_key_t key) { diff --git a/kandinsky/Makefile b/kandinsky/Makefile index c361f6fe0..a9cfd3593 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -1,7 +1,9 @@ SFLAGS += -Ikandinsky/include objs += $(addprefix kandinsky/src/,\ + color.o\ context.o\ font.o\ + framebuffer.o\ line.o\ pixel.o\ rect.o\ diff --git a/kandinsky/include/kandinsky.h b/kandinsky/include/kandinsky.h index b3c7d5abe..e99ceeb2e 100644 --- a/kandinsky/include/kandinsky.h +++ b/kandinsky/include/kandinsky.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/kandinsky/include/kandinsky/color.h b/kandinsky/include/kandinsky/color.h index df47877ca..9b8c1e6b4 100644 --- a/kandinsky/include/kandinsky/color.h +++ b/kandinsky/include/kandinsky/color.h @@ -11,8 +11,13 @@ typedef uint16_t KDColor; * Given KDColor is RGB565 and that we take 8-bit values for R, G, and B, * some shifting has to happen. */ +// FIXME: KDColorRGB(rgb), as a single 32-bits value #define KDColorRGB(r,g,b) ((KDColor)((((((uint16_t)(r))>>3)&0x1F)<<11)|(((((uint16_t)(g))>>2)&0x3F)<<5)|((((uint16_t)(b))>>3)&0x1F))) +#define KDColorRedComponent(color) ((uint8_t)(((color)>>11)<<3)) +#define KDColorGreenComponent(color) ((uint8_t)((((color)>>5)&0x3F)<<2)) +#define KDColorBlueComponent(color) ((uint8_t)(((color)&0x1F)<<3)) + #define KDColorGray(i) KDColorRGB(i,i,i) #define KDColorBlack KDColorRGB(0x00, 0x00, 0x00) @@ -22,4 +27,8 @@ typedef uint16_t KDColor; #define KDColorGreen KDColorRGB(0x00, 0xFF, 0x00) #define KDColorBlue KDColorRGB(0x00, 0x00, 0xFF) +// alpha = 0 -> only a +// alpha = 0xFF -> only b +KDColor KDColorBlend(KDColor background, KDColor foreground, uint8_t alpha); + #endif diff --git a/kandinsky/include/kandinsky/context.h b/kandinsky/include/kandinsky/context.h index 7780a2c4d..4d3716c85 100644 --- a/kandinsky/include/kandinsky/context.h +++ b/kandinsky/include/kandinsky/context.h @@ -5,12 +5,11 @@ #include "rect.h" typedef struct { - void (*setPixel)(KDCoordinate x, KDCoordinate y, KDColor color); - // fillRect can be left NULL. - // In that case, Kandinsky will fall back to using setPixel only - void (*fillRect)(KDCoordinate x, KDCoordinate y, - KDCoordinate width, KDCoordinate height, - KDColor * pattern, size_t patternSize); + struct { + void (*pushRect)(KDRect rect, const KDColor * pixels); + void (*pushRectUniform)(KDRect rect, KDColor color); + void (*pullRect)(KDRect rect, KDColor * pixels); + } io; KDPoint origin; KDRect clippingRect; } KDContext; diff --git a/kandinsky/include/kandinsky/framebuffer.h b/kandinsky/include/kandinsky/framebuffer.h new file mode 100644 index 000000000..04335ff2c --- /dev/null +++ b/kandinsky/include/kandinsky/framebuffer.h @@ -0,0 +1,17 @@ +#ifndef KANDINSKY_FRAMEBUFFER_H +#define KANDINSKY_FRAMEBUFFER_H + +#include +#include +#include + +typedef struct { + KDColor * pixels; + KDSize size; +} KDFrameBuffer; + +void KDFramePushRect(KDFrameBuffer * frameBuffer, KDRect rect, const KDColor * pixels); +void KDFramePushRectUniform(KDFrameBuffer * frameBuffer, KDRect rect, KDColor color); +void KDFramePullRect(KDFrameBuffer * frameBuffer, KDRect rect, KDColor * pixels); + +#endif diff --git a/kandinsky/include/kandinsky/line.h b/kandinsky/include/kandinsky/line.h index 069c1e0ac..7affd543e 100644 --- a/kandinsky/include/kandinsky/line.h +++ b/kandinsky/include/kandinsky/line.h @@ -6,4 +6,6 @@ void KDDrawLine(KDPoint p1, KDPoint p2, KDColor c); +void KDDrawAntiAliasedLine(KDPoint p1, KDPoint p2, KDCoordinate width, KDColor frontColor, KDColor backColor); + #endif diff --git a/kandinsky/include/kandinsky/pixel.h b/kandinsky/include/kandinsky/pixel.h index d3e3a045d..f12762880 100644 --- a/kandinsky/include/kandinsky/pixel.h +++ b/kandinsky/include/kandinsky/pixel.h @@ -5,5 +5,6 @@ #include void KDSetPixel(KDPoint p, KDColor c); +KDColor KDGetPixel(KDPoint p); #endif diff --git a/kandinsky/include/kandinsky/rect.h b/kandinsky/include/kandinsky/rect.h index a77118359..922e9f170 100644 --- a/kandinsky/include/kandinsky/rect.h +++ b/kandinsky/include/kandinsky/rect.h @@ -34,7 +34,9 @@ KDRect KDRectUnion(KDRect r1, KDRect r2); bool KDRectContains(KDRect r, KDPoint p); KDRect KDRectTranslate(KDRect r, KDPoint p); -void KDFillRect(KDRect rect, const KDColor * pattern, size_t patternSize); +void KDFillRect(KDRect rect, KDColor color); +void KDFillRectWithPixels(KDRect rect, const KDColor * pixels, KDColor * workingBuffer); +void KDFillRectWithMask(KDRect rect, KDColor color, const uint8_t * mask, KDColor * workingBuffer); void KDDrawRect(KDRect rect, KDColor color); #endif diff --git a/kandinsky/src/color.c b/kandinsky/src/color.c new file mode 100644 index 000000000..890bc89cb --- /dev/null +++ b/kandinsky/src/color.c @@ -0,0 +1,11 @@ +#include + +#define KDAlphaBlend(c1,c2,a) ((uint8_t)((((uint16_t)c1*(0xFF-a))+((uint16_t)c2*a))/0xFF)) + +KDColor KDColorBlend(KDColor a, KDColor b, uint8_t alpha) { + return KDColorRGB( + KDAlphaBlend(KDColorRedComponent(a), KDColorRedComponent(b), alpha), + KDAlphaBlend(KDColorGreenComponent(a), KDColorGreenComponent(b), alpha), + KDAlphaBlend(KDColorBlueComponent(a), KDColorBlueComponent(b), alpha) + ); +} diff --git a/kandinsky/src/context.c b/kandinsky/src/context.c index f9303b77b..9e42c2b1f 100644 --- a/kandinsky/src/context.c +++ b/kandinsky/src/context.c @@ -2,8 +2,11 @@ #include KDContext KDIonContext = { - .setPixel = &ion_set_pixel, - .fillRect = &ion_fill_rect, + .io = { + .pushRect = &ion_screen_push_rect, + .pushRectUniform = &ion_screen_push_rect_uniform, + .pullRect = &ion_screen_pull_rect + }, .origin = { .x = 0, .y = 0 diff --git a/kandinsky/src/framebuffer.c b/kandinsky/src/framebuffer.c new file mode 100644 index 000000000..b4d77feb5 --- /dev/null +++ b/kandinsky/src/framebuffer.c @@ -0,0 +1,34 @@ +#include +#include + +static inline KDColor * frameBufferPixelAddress(KDFrameBuffer * frameBuffer, KDCoordinate x, KDCoordinate y) { + return frameBuffer->pixels + x + y*frameBuffer->size.width; +} + +void KDFramePushRect(KDFrameBuffer * frameBuffer, KDRect rect, const KDColor * pixels) { + const KDColor * line = pixels; + for (int j=0; j +#include + +void KDDrawAntiAliasedLine(KDPoint p1, KDPoint p2, KDCoordinate width, KDColor frontColor, KDColor backColor) { + int x0 = p1.x; + int y0 = p1.y; + int x1 = p2.x; + int y1 = p2.y; + float wd = width; + + int dx = abs(x1-x0), sx = x0 < x1 ? 1 : -1; + int dy = abs(y1-y0), sy = y0 < y1 ? 1 : -1; + int err = dx-dy, e2, x2, y2; // error value e_xy + float ed = dx+dy == 0 ? 1 : sqrt((float)dx*dx+(float)dy*dy); + + for (wd = (wd+1)/2; ; ) { // pixel loop + KDPoint p = {.x = x0, .y = y0}; + KDColor color = KDColorMix(KDColorRed, KDColorWhite, (abs(err-dx+dy)/ed-wd+1)); + KDSetPixel(p, color); + e2 = err; x2 = x0; + if (2*e2 >= -dx) { // x step + for (e2 += dy, y2 = y0; e2 < ed*wd && (y1 != y2 || dx > dy); e2 += dx) { + y2 += sy; + p = {.x = x0, .y = y2}; + color = KDColorMix(KDColorRed, KDColorWhite, (abs(err-dx+dy)/ed-wd+1)); + setPixelColor(x0, y2 += sy, max(0,255*(abs(e2)/ed-wd+1))); + } + if (x0 == x1) break; + e2 = err; err -= dy; x0 += sx; + } + if (2*e2 <= dy) { // y step + for (e2 = dx-e2; e2 < ed*wd && (x1 != x2 || dx < dy); e2 += dy) + setPixelColor(x2 += sx, y0, max(0,255*(abs(e2)/ed-wd+1))); + if (y0 == y1) break; + err += dx; y0 += sy; + } + } +} +*/ diff --git a/kandinsky/src/pixel.c b/kandinsky/src/pixel.c index 76198b31a..d162ce0e9 100644 --- a/kandinsky/src/pixel.c +++ b/kandinsky/src/pixel.c @@ -7,6 +7,29 @@ void KDSetPixel(KDPoint p, KDColor c) { KDPoint absolutePoint = KDPointTranslate(p, KDCurrentContext->origin); if (KDRectContains(KDCurrentContext->clippingRect, absolutePoint)) { - KDCurrentContext->setPixel(absolutePoint.x, absolutePoint.y, c); + KDCurrentContext->io.pushRect((KDRect){.origin = absolutePoint, .width = 1, .height = 1}, &c); } } + +KDColor KDGetPixel(KDPoint p) { + KDPoint absolutePoint = KDPointTranslate(p, KDCurrentContext->origin); + if (KDRectContains(KDCurrentContext->clippingRect, absolutePoint)) { + KDColor result; + KDCurrentContext->io.pullRect((KDRect){.origin = absolutePoint, .width = 1, .height = 1}, &result); + return result; + } + return KDColorBlack; +} +/* +void KDSetAbsolutePixelDirect(KDPoint p, KDColor c) { + KDCurrentContext->io.setWorkingArea((KDRect){.origin = p, .width = 1, .height = 1}); + KDCurrentContext->io.pushPixels(&c, 1); +} + +KDColor KDGetAbsolutePixelDirect(KDPoint p) { + KDColor result; + KDCurrentContext->io.setWorkingArea((KDRect){.origin = p, .width = 1, .height = 1}); + KDCurrentContext->io.pullPixels(&result, 1); + return result; +} +*/ diff --git a/kandinsky/src/rect.c b/kandinsky/src/rect.c index dfe2ab72c..ef4b64e6a 100644 --- a/kandinsky/src/rect.c +++ b/kandinsky/src/rect.c @@ -101,38 +101,150 @@ KDRect KDRectTranslate(KDRect r, KDPoint p) { }; } -void KDPerPixelFillRect(KDCoordinate x, KDCoordinate y, - KDCoordinate width, KDCoordinate height, - KDColor * pattern, size_t patternSize) { - size_t offset = 0; - for (KDCoordinate j=0; jsetPixel(x+i, y+j, pattern[offset++]); - if (offset >= patternSize) { - offset = 0; - } +KDRect absoluteFillRect(KDRect rect) { + KDRect absolutRect = rect; + absolutRect.origin = KDPointTranslate(absolutRect.origin, KDCurrentContext->origin); + + KDRect rectToBeFilled = KDRectIntersection(absolutRect, KDCurrentContext->clippingRect); + return rectToBeFilled; +} + +void KDFillRect(KDRect rect, KDColor color) { + KDRect absoluteRect = absoluteFillRect(rect); + if (absoluteRect.width > 0 && absoluteRect.height > 0) { + KDCurrentContext->io.pushRectUniform(absoluteRect, color); + } +} + +/* Note: we support the case where workingBuffer IS equal to pixels */ +void KDFillRectWithPixels(KDRect rect, const KDColor * pixels, KDColor * workingBuffer) { + KDRect absoluteRect = absoluteFillRect(rect); + + if (absoluteRect.width == 0 || absoluteRect.height == 0) { + return; + } + + /* Caution: + * The absoluteRect may have a SMALLER size than the original rect because it + * has been clipped. Therefore we cannot assume that the mask can be read as a + * continuous area. */ + + if (absoluteRect.width == rect.width && absoluteRect.height == rect.height) { + KDCurrentContext->io.pushRect(absoluteRect, pixels); + return; + } + + /* At this point we need the working buffer */ + assert(workingBuffer != NULL); + for (KDCoordinate j=0; jio.pushRect(absoluteRect, workingBuffer); +} + +// Mask's size must be rect.size +// WorkingBuffer, same deal +void KDFillRectWithMask(KDRect rect, KDColor color, const uint8_t * mask, KDColor * workingBuffer) { + KDRect absoluteRect = absoluteFillRect(rect); + + /* Caution: + * The absoluteRect may have a SMALLER size than the original rect because it + * has been clipped. Therefore we cannot assume that the mask can be read as a + * continuous area. */ + + KDCurrentContext->io.pullRect(absoluteRect, workingBuffer); + for (KDCoordinate j=0; jio.pushRect(absoluteRect, workingBuffer); +} + +#if 0 + +/* Takes an absolute rect and pushes the pixels */ +/* Does the pattern-to-continuous-memory conversion */ +void pushRect(KDRect rect, const KDColor * pattern, KDSize patternSize) { + KDCurrentContext->io.setWorkingArea(rect); +//#define ION_DEVICE_FILL_RECT_FAST_PATH 1 +#if ION_DEVICE_FILL_RECT_FAST_PATH + /* If the pattern width matches the target rect width, we can easily push + * mutliple lines at once since those will be contiguous in memory. */ + if (patternSize.width == rect.width) { + size_t remainingPixelCount = rect.width*rect.height; + size_t patternPixelCount = patternSize.width*patternSize.height; + while (remainingPixelCount > 0) { + int32_t blockSize = remainingPixelCount < patternPixelCount ? remainingPixelCount : patternPixelCount; + KDCurrentContext->io.pushPixels(pattern, blockSize); + remainingPixelCount -= blockSize; + } + return; + } +#endif + uint16_t remainingHeight = rect.height; + uint16_t patternLine = 0; + while (remainingHeight-- > 0) { + uint16_t remainingWidth = rect.width; + while (remainingWidth > 0) { + int32_t blockSize = remainingWidth < patternSize.width ? remainingWidth : patternSize.width; + KDCurrentContext->io.pushPixels(pattern + patternLine*patternSize.width, blockSize); + remainingWidth -= blockSize; + } + if (++patternLine >= patternSize.height) { + patternLine = 0; } } } -void KDFillRect(KDRect rect, const KDColor * pattern, size_t patternSize) { - assert(patternSize >= 1); +void KDBlitRect(KDRect rect, const KDColor * pattern, KDSize patternSize, const uint8_t * mask, KDSize maskSize) { + assert(pattern != NULL && patternSize.width > 0 && patternSize.height > 0); KDRect absolutRect = rect; absolutRect.origin = KDPointTranslate(absolutRect.origin, KDCurrentContext->origin); KDRect rectToBeFilled = KDRectIntersection(absolutRect, KDCurrentContext->clippingRect); - void (*fillRectFunction)(KDCoordinate x, KDCoordinate y, - KDCoordinate width, KDCoordinate height, - KDColor * pattern, size_t patternSize) = KDCurrentContext->fillRect; - if (fillRectFunction == NULL) { - fillRectFunction = KDPerPixelFillRect; + bool useBlending = (mask != NULL && maskSize.width > 0 && maskSize.height > 0); + + if (!useBlending) { + pushRect(rectToBeFilled, pattern, patternSize); + return; } - fillRectFunction(rectToBeFilled.x, rectToBeFilled.y, - rectToBeFilled.width, rectToBeFilled.height, - pattern, patternSize); + assert(KDCurrentContext->io.pullPixels != NULL); + + KDCoordinate patternX = 0, patternY = 0, maskX = 0, maskY = 0; + for (KDCoordinate j=0; j= patternSize.width) { + patternX = 0; + } + if (++maskX >= maskSize.width) { + maskX = 0; + } + } + if (++patternY >= patternSize.height) { + patternY = 0; + } + if (++maskY >= maskSize.height) { + maskY = 0; + } + } } +#endif void KDDrawRect(KDRect rect, KDColor color) { KDPoint p1, p2; diff --git a/liba/Makefile b/liba/Makefile index 427b1b432..db13011f5 100644 --- a/liba/Makefile +++ b/liba/Makefile @@ -4,3 +4,7 @@ liba/src/external/sqlite/mem5.o: CFLAGS += -w objs += $(addprefix liba/src/, assert.o errno.o malloc.o memcpy.o memset.o powf.o strcmp.o strlen.o external/sqlite/mem5.o) tests += $(addprefix liba/test/, stdint.c) + +# The use of aeabi-rt could be made conditional to an AEABI target. +# In practice we're always using liba on such a target. +objs += $(addprefix liba/src/aeabi-rt/, memcpy.o) diff --git a/liba/src/aeabi-rt/README.txt b/liba/src/aeabi-rt/README.txt new file mode 100644 index 000000000..be3109f12 --- /dev/null +++ b/liba/src/aeabi-rt/README.txt @@ -0,0 +1,16 @@ +We're using the ARM Embedded ABI (AEABI) to build our code for the Cortex-M. +That ABI specifies a lot of things: how procedures are called, how ELF files +should be structured, and last but not least, a run-time. + +That run-time consists in a list of helper functions that the compiler is free +to call anywhere : unaligned memory accesses, integer division functions, +memory copying/clearing/setting, etc... + +Since we're telling our compiler to build an AEABI binary, it may decide to use +those symbols, and so we have to provide an implementation for them. Refer to +the "Run-time ABI for the ARM Architecture" for a full list of functions. + +Note that this is not formally the job of a libc implementation. Similar code is +often shipped alongside a compiler (LLVM calls it compiler-rt, GCC libgcc). But +since we're using so few symbols in practice, it makes sense to glue it in. Also +some libc do also implement this (newlib, for example). diff --git a/liba/src/aeabi-rt/memcpy.c b/liba/src/aeabi-rt/memcpy.c new file mode 100644 index 000000000..0b62598cc --- /dev/null +++ b/liba/src/aeabi-rt/memcpy.c @@ -0,0 +1,8 @@ +#include +#include + +/* See the "Run-time ABI for the ARM Architecture", Section 4.3.3 */ + +void __aeabi_memcpy(void * dest, const void * src, size_t n) { + memcpy(dest, src, n); +}