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
)
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)

View File

@ -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];
};
}

View File

@ -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<pixel>& texture, bool ignore_winding = true);
void draw_triangle(const triangle& tri, const buffer<pixel>& texture, bool ignore_winding = true);
private:
oled* screen;
oled& screen;
};
}

View File

@ -2,86 +2,132 @@
#include "picoled/buffer.h"
#include <cmath>
#include <algorithm>
#include <pico/stdio.h>
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 };
// 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;
}
else
bool triangle::left_sided() const
{
return { a, b, c };
}
// *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<pixel>& 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);
// 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)
{
const fixed left = y * upper_left_slope + sorted_tri.a.position.x;
const fixed right = y * upper_right_slope + sorted_tri.a.position.x;
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 });
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)
{
screen.write(x, y + sorted_tri.a.position.y, texture.read_uv(u, v));
u += u_delta;
v += v_delta;
}
}
vec2 triangle::min() const
{
return
{
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)
};
}
// 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);
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 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;
fixed triangle::area_double() const
// render lower triangle
for (fixed y = 0; y < sorted_tri.c.position.y - sorted_tri.b.position.y + fixed(0.2f); y += 1)
{
// 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));
}
// 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;
void gfx2d::draw_triangle(triangle tri, const buffer<pixel>& texture, bool ignore_winding)
{
if (ignore_winding) { tri = tri.fix_winding(); }
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 vec2 min = tri.min();
const vec2 max = tri.max();
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 };
for (fixed i = min.x; i <= max.x; i += 1)
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)
{
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);
}
// 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 <hardware/dma.h>
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 };
}