Re-add texturing

This commit is contained in:
shylie 2025-06-16 10:41:15 -04:00
parent 846f909cfe
commit 196160f37b
5 changed files with 168 additions and 71 deletions

View File

@ -14,5 +14,10 @@ add_library(picoled
src/ST7789.cpp 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) target_include_directories(picoled PUBLIC include)

View File

@ -30,8 +30,12 @@ private:
spi_inst_t* spi; spi_inst_t* spi;
uint8_t cs_pin; uint8_t cs_pin;
uint8_t dc_pin; uint8_t dc_pin;
uint8_t dma_tx;
static pixel format(const pixel& p); static pixel format(const pixel& p);
static void dma_handler();
static int cs_pins[12];
}; };
} }

View File

@ -54,27 +54,23 @@ public:
class triangle class triangle
{ {
friend class gfx2d;
public: public:
vertex2 a; vertex2 a;
vertex2 b; vertex2 b;
vertex2 c; 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: private:
fixed area_double() const; triangle sort_y() const;
bool left_sided() const;
vec3 barycentric(const vec2& p) const;
}; };
class gfx2d class gfx2d
{ {
public: public:
explicit gfx2d(oled& screen) : explicit gfx2d(oled& screen) :
screen(&screen) screen(screen)
{} {}
gfx2d(const gfx2d&) = delete; gfx2d(const gfx2d&) = delete;
@ -83,10 +79,10 @@ public:
gfx2d& operator=(const gfx2d&) = delete; gfx2d& operator=(const gfx2d&) = delete;
gfx2d& operator=(gfx2d&&) = delete; gfx2d& operator=(gfx2d&&) = delete;
void draw_triangle(triangle tri, const buffer<pixel>& texture, bool ignore_winding = true); void draw_triangle(const triangle& tri, const buffer<pixel>& texture, bool ignore_winding = true);
private: private:
oled* screen; oled& screen;
}; };
} }

View File

@ -2,87 +2,133 @@
#include "picoled/buffer.h" #include "picoled/buffer.h"
#include <cmath> #include <cmath>
#include <algorithm>
#include <pico/stdio.h> #include <pico/stdio.h>
namespace picoled::gfx namespace picoled::gfx
{ {
triangle triangle::fix_winding() const triangle triangle::sort_y() const
{ {
if (((b.position - a.position) ^ (c.position - a.position)) < fixed(0)) // sort from top to bottom (lowest -> highest y)
{ triangle t = *this;
return { a, c, b };
} // sort a
else 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); }
return { a, b, 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 vec3 triangle::barycentric(const vec2& p) const
{ {
const fixed total = area_double(); const vec2 ba = b.position - a.position;
const fixed u = triangle{ b, a, p }.area_double() / total; const vec2 ca = c.position - a.position;
const fixed v = triangle{ a, c, p }.area_double() / total; const vec2 pa = p - a.position;
return vec3{ u, v, fixed(1) - u - v };
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<pixel>& texture, bool ignore_winding)
{ {
const fixed abp = (b.position - a.position) ^ (p - a.position); const triangle sorted_tri = tri.sort_y();
const fixed bcp = (c.position - b.position) ^ (p - b.position); const bool left_sided = sorted_tri.left_sided();
const fixed cap = (a.position - c.position) ^ (p - c.position);
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 // 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);
return
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), const fixed left = y * upper_left_slope + sorted_tri.a.position.x;
fixed::min(fixed::min(a.position.y, b.position.y), c.position.y) const fixed right = y * upper_right_slope + sorted_tri.a.position.x;
};
}
vec2 triangle::max() const 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 });
return
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)
{ {
fixed::max(fixed::max(a.position.x, b.position.x), c.position.x), screen.write(x, y + sorted_tri.a.position.y, texture.read_uv(u, v));
fixed::max(fixed::max(a.position.y, b.position.y), c.position.y) u += u_delta;
}; v += v_delta;
}
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));
}
void gfx2d::draw_triangle(triangle tri, const buffer<pixel>& texture, bool ignore_winding)
{
if (ignore_winding) { tri = tri.fix_winding(); }
const vec2 min = tri.min();
const vec2 max = tri.max();
for (fixed i = min.x; i <= max.x; i += 1)
{
for (fixed j = min.y; j <= max.y; j += 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);
} }
} }
// 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;
}
} }
} }

View File

@ -2,6 +2,8 @@
#include <pico/stdlib.h> #include <pico/stdlib.h>
#include <hardware/dma.h>
namespace picoled 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), oled(width, height),
spi(spi), spi(spi),
cs_pin(cs_pin), 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(0x01); sleep_ms(150); // reset device
command(0x11); sleep_ms(10); // stop sleeping 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(0x21); // turn on display inversion
command(0x13); sleep_ms(10); // set display to normal mode command(0x13); sleep_ms(10); // set display to normal mode
command(0x29); sleep_ms(10); // turn on display 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 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 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) pixel st7789::format(const pixel& p)
{ {
/*
const uint16_t red_scaled = p.red() * 0x3F / 0xFF; // scale to 6-bit color 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 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 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 red = (red_scaled & 0xFF) << 2;
const uint8_t green = (green_scaled & 0xFF) << 2; const uint8_t green = (green_scaled & 0xFF) << 2;
const uint8_t blue = (blue_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 }; 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 };
} }