From 196160f37b4d1a7187a9fe8a1d7535dfa2a13181 Mon Sep 17 00:00:00 2001 From: shylie Date: Mon, 16 Jun 2025 10:41:15 -0400 Subject: [PATCH] Re-add texturing --- CMakeLists.txt | 7 +- include/picoled/ST7789.h | 4 + include/picoled/gfx/2D.h | 18 ++--- src/2D.cpp | 160 +++++++++++++++++++++++++-------------- src/ST7789.cpp | 50 +++++++++++- 5 files changed, 168 insertions(+), 71 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eae5888..21fa2b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,5 +14,10 @@ add_library(picoled src/ST7789.cpp ) -target_link_libraries(picoled PUBLIC pico_stdlib hardware_i2c hardware_spi) +target_link_libraries(picoled PUBLIC + pico_stdlib + hardware_i2c + hardware_spi + hardware_dma +) target_include_directories(picoled PUBLIC include) diff --git a/include/picoled/ST7789.h b/include/picoled/ST7789.h index ddf7b46..858fba8 100644 --- a/include/picoled/ST7789.h +++ b/include/picoled/ST7789.h @@ -30,8 +30,12 @@ private: spi_inst_t* spi; uint8_t cs_pin; uint8_t dc_pin; + uint8_t dma_tx; static pixel format(const pixel& p); + + static void dma_handler(); + static int cs_pins[12]; }; } diff --git a/include/picoled/gfx/2D.h b/include/picoled/gfx/2D.h index 4403578..360512a 100644 --- a/include/picoled/gfx/2D.h +++ b/include/picoled/gfx/2D.h @@ -54,27 +54,23 @@ public: class triangle { + friend class gfx2d; public: vertex2 a; vertex2 b; vertex2 c; - triangle fix_winding() const; - vec3 barycentric(const vec2& p) const; - bool contains(const vec2& p) const; - - vec2 min() const; - vec2 max() const; - private: - fixed area_double() const; + triangle sort_y() const; + bool left_sided() const; + vec3 barycentric(const vec2& p) const; }; class gfx2d { public: explicit gfx2d(oled& screen) : - screen(&screen) + screen(screen) {} gfx2d(const gfx2d&) = delete; @@ -83,10 +79,10 @@ public: gfx2d& operator=(const gfx2d&) = delete; gfx2d& operator=(gfx2d&&) = delete; - void draw_triangle(triangle tri, const buffer& texture, bool ignore_winding = true); + void draw_triangle(const triangle& tri, const buffer& texture, bool ignore_winding = true); private: - oled* screen; + oled& screen; }; } diff --git a/src/2D.cpp b/src/2D.cpp index 4a5fd89..72b1175 100644 --- a/src/2D.cpp +++ b/src/2D.cpp @@ -2,86 +2,132 @@ #include "picoled/buffer.h" #include +#include #include namespace picoled::gfx { -triangle triangle::fix_winding() const +triangle triangle::sort_y() const { - if (((b.position - a.position) ^ (c.position - a.position)) < fixed(0)) - { - return { a, c, b }; - } - else - { - return { a, b, c }; - } + // sort from top to bottom (lowest -> highest y) + triangle t = *this; + + // sort a + if (t.a.position.y > t.b.position.y) { std::swap(t.a, t.b); } + if (t.b.position.y > t.c.position.y) { std::swap(t.b, t.c); } + + // sort b and c + if (t.a.position.y > t.b.position.y) { std::swap(t.a, t.b); } + + return t; +} + +bool triangle::left_sided() const +{ + // *this must be sorted by y + + const vertex2* leftmost = &a; + if (b.position.x < leftmost->position.x) { leftmost = &b; } + if (c.position.x < leftmost->position.x) { leftmost = &c; } + + return leftmost == &a || leftmost == &c; } vec3 triangle::barycentric(const vec2& p) const { - const fixed total = area_double(); - const fixed u = triangle{ b, a, p }.area_double() / total; - const fixed v = triangle{ a, c, p }.area_double() / total; - return vec3{ u, v, fixed(1) - u - v }; + const vec2 ba = b.position - a.position; + const vec2 ca = c.position - a.position; + const vec2 pa = p - a.position; + + const fixed denominator_inv = fixed(1) / (ba.x * ca.y - ca.x * ba.y); + + const fixed a = (pa.x * ca.y - ca.x * pa.y) * denominator_inv; + const fixed b = (ba.x * pa.y - pa.x * ba.y) * denominator_inv; + + return { a, b, fixed(1) - a - b }; } -bool triangle::contains(const vec2& p) const +void gfx2d::draw_triangle(const triangle& tri, const buffer& texture, bool ignore_winding) { - const fixed abp = (b.position - a.position) ^ (p - a.position); - const fixed bcp = (c.position - b.position) ^ (p - b.position); - const fixed cap = (a.position - c.position) ^ (p - c.position); + const triangle sorted_tri = tri.sort_y(); + const bool left_sided = sorted_tri.left_sided(); - return abp >= fixed(0) && bcp >= fixed(0) && cap >= fixed(0); -} + // tall segment + const fixed inv_slope_ac = (sorted_tri.c.position.x - sorted_tri.a.position.x) / (sorted_tri.c.position.y - sorted_tri.a.position.y); -vec2 triangle::min() const -{ - return + // top other side segment + const fixed inv_slope_ab = (sorted_tri.b.position.x - sorted_tri.a.position.x) / (sorted_tri.b.position.y - sorted_tri.a.position.y); + + const fixed upper_left_slope = left_sided ? inv_slope_ac : inv_slope_ab; + const fixed upper_right_slope = left_sided ? inv_slope_ab : inv_slope_ac; + + // render upper triangle + for (fixed y = 0; y < sorted_tri.b.position.y - sorted_tri.a.position.y + fixed(0.2f); y += 1) { - fixed::min(fixed::min(a.position.x, b.position.x), c.position.x), - fixed::min(fixed::min(a.position.y, b.position.y), c.position.y) - }; -} + const fixed left = y * upper_left_slope + sorted_tri.a.position.x; + const fixed right = y * upper_right_slope + sorted_tri.a.position.x; -vec2 triangle::max() const -{ - return - { - fixed::max(fixed::max(a.position.x, b.position.x), c.position.x), - fixed::max(fixed::max(a.position.y, b.position.y), c.position.y) - }; -} + const vec3 barycentric_left = tri.barycentric({ left, y + sorted_tri.a.position.y }); + const vec3 barycentric_right = tri.barycentric({ right, y + sorted_tri.a.position.y }); -fixed triangle::area_double() const -{ - // don't divide by two since it would cancel out - // in the barycentric coordinate calculation anyway - return std::abs((b.position - a.position) ^ (c.position - a.position)); -} + const fixed u_left = barycentric_left % vec3{ tri.a.texture_coordinates.x, tri.b.texture_coordinates.x, tri.c.texture_coordinates.x }; + const fixed v_left = barycentric_left % vec3{ tri.a.texture_coordinates.y, tri.b.texture_coordinates.y, tri.c.texture_coordinates.y }; -void gfx2d::draw_triangle(triangle tri, const buffer& texture, bool ignore_winding) -{ - if (ignore_winding) { tri = tri.fix_winding(); } + const fixed u_right = barycentric_right % vec3{ tri.a.texture_coordinates.x, tri.b.texture_coordinates.x, tri.c.texture_coordinates.x }; + const fixed v_right = barycentric_right % vec3{ tri.a.texture_coordinates.y, tri.b.texture_coordinates.y, tri.c.texture_coordinates.y }; - const vec2 min = tri.min(); - const vec2 max = tri.max(); + const fixed distance_inv = fixed(1) / (right - left); + const fixed u_delta = (u_right - u_left) * distance_inv; + const fixed v_delta = (v_right - v_left) * distance_inv; - for (fixed i = min.x; i <= max.x; i += 1) - { - for (fixed j = min.y; j <= max.y; j += 1) + fixed u = u_left; + fixed v = v_left; + + for (fixed x = left; x < right; x += 1) { - const vec2 position = { i, j }; - if (tri.contains(position)) - { - const vec3 barycentric = tri.barycentric(position); - const fixed u = barycentric % vec3{ tri.a.texture_coordinates.x, tri.b.texture_coordinates.x, tri.c.texture_coordinates.x }; - const fixed v = barycentric % vec3{ tri.a.texture_coordinates.y, tri.b.texture_coordinates.y, tri.c.texture_coordinates.y }; - const pixel read = texture.read_uv(u, v, buffer_read_mode::clamp); - screen->write(i, j, read); - } + screen.write(x, y + sorted_tri.a.position.y, texture.read_uv(u, v)); + u += u_delta; + v += v_delta; + } + } + + // bottom side other segment + const fixed inv_slope_bc = (sorted_tri.c.position.x - sorted_tri.b.position.x) / (sorted_tri.c.position.y - sorted_tri.b.position.y); + + const fixed lower_left_slope = left_sided ? inv_slope_ac : inv_slope_bc; + const fixed lower_right_slope = left_sided ? inv_slope_bc : inv_slope_ac; + + // render lower triangle + for (fixed y = 0; y < sorted_tri.c.position.y - sorted_tri.b.position.y + fixed(0.2f); y += 1) + { + // going from bottom to top, multiply y by -1 + const fixed left = -y * lower_left_slope + sorted_tri.c.position.x; + const fixed right = -y * lower_right_slope + sorted_tri.c.position.x; + + const vec3 barycentric_left = tri.barycentric({ left, -y + sorted_tri.c.position.y }); + const vec3 barycentric_right = tri.barycentric({ right, -y + sorted_tri.c.position.y }); + + const fixed u_left = barycentric_left % vec3{ tri.a.texture_coordinates.x, tri.b.texture_coordinates.x, tri.c.texture_coordinates.x }; + const fixed v_left = barycentric_left % vec3{ tri.a.texture_coordinates.y, tri.b.texture_coordinates.y, tri.c.texture_coordinates.y }; + + const fixed u_right = barycentric_right % vec3{ tri.a.texture_coordinates.x, tri.b.texture_coordinates.x, tri.c.texture_coordinates.x }; + const fixed v_right = barycentric_right % vec3{ tri.a.texture_coordinates.y, tri.b.texture_coordinates.y, tri.c.texture_coordinates.y }; + + const fixed distance_inv = fixed(1) / (right - left); + const fixed u_delta = (u_right - u_left) * distance_inv; + const fixed v_delta = (v_right - v_left) * distance_inv; + + fixed u = u_left; + fixed v = v_left; + + for (fixed x = left; x < right; x += 1) + { + // going from bottom to top, multiply y by -1 + screen.write(x, -y + sorted_tri.c.position.y, texture.read_uv(u, v)); + u += u_delta; + v += v_delta; } } } diff --git a/src/ST7789.cpp b/src/ST7789.cpp index e07db4b..1facdbe 100644 --- a/src/ST7789.cpp +++ b/src/ST7789.cpp @@ -2,6 +2,8 @@ #include +#include + namespace picoled { @@ -9,7 +11,8 @@ st7789::st7789(size_t width, size_t height, spi_inst_t* spi, uint8_t cs_pin, uin oled(width, height), spi(spi), cs_pin(cs_pin), - dc_pin(dc_pin) + dc_pin(dc_pin), + dma_tx(dma_claim_unused_channel(true)) { command(0x01); sleep_ms(150); // reset device command(0x11); sleep_ms(10); // stop sleeping @@ -46,11 +49,31 @@ st7789::st7789(size_t width, size_t height, spi_inst_t* spi, uint8_t cs_pin, uin command(0x21); // turn on display inversion command(0x13); sleep_ms(10); // set display to normal mode command(0x29); sleep_ms(10); // turn on display + + dma_channel_config c = dma_channel_get_default_config(dma_tx); + channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_dreq(&c, spi_get_dreq(spi, true)); + dma_channel_configure(dma_tx, &c, &spi_get_hw(spi)->dr, nullptr, 3 * get_width() * get_height(), false); + + dma_channel_set_irq0_enabled(dma_tx, true); + + irq_add_shared_handler(DMA_IRQ_0, dma_handler, 0); + + cs_pins[dma_tx] = cs_pin; } void st7789::update_impl() const { - command(0x2C, inactive_buffer().data_pointer(), get_width() * get_height() * 3); + dma_channel_wait_for_finish_blocking(dma_tx); // wait for previous frame to finish + + gpio_put(cs_pin, false); // select chip + + gpio_put(dc_pin, false); // command mode + constexpr uint8_t cmd = 0x2C; + spi_write_blocking(spi, &cmd, 1); + + gpio_put(dc_pin, true); // parameters are sent via data mode + dma_channel_set_read_addr(dma_tx, inactive_buffer().data_pointer(), true); // upload frame } void st7789::command(uint8_t cmd) const @@ -78,6 +101,7 @@ void st7789::command(uint8_t cmd, const uint8_t* params, size_t param_count) con pixel st7789::format(const pixel& p) { + /* const uint16_t red_scaled = p.red() * 0x3F / 0xFF; // scale to 6-bit color const uint16_t green_scaled = p.green() * 0x3F / 0xFF; // scale to 6-bit color const uint16_t blue_scaled = p.blue() * 0x3F / 0xFF; // scale to 6-bit color @@ -85,8 +109,30 @@ pixel st7789::format(const pixel& p) const uint8_t red = (red_scaled & 0xFF) << 2; const uint8_t green = (green_scaled & 0xFF) << 2; const uint8_t blue = (blue_scaled & 0xFF) << 2; + */ + + const uint8_t red = p.red() & 0xFC; + const uint8_t green = p.green() & 0xFC; + const uint8_t blue = p.blue() & 0xFC; return { red, green, blue }; } +void st7789::dma_handler() +{ + for (int i = 0; i < 12; i++) + { + if (dma_hw->ints0 & (1 << i)) + { + // clear interrupt + dma_hw->ints0 = 1 << i; + + // deselect chip + gpio_put(cs_pins[i], true); + } + } +} + +int st7789::cs_pins[12] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + }