gekko: soft filter and video gamma on the Wii

The A/V encoder's composite video trap filter follows Soft Filter, and
Video Gamma sets its gamma curve, 0.1 to 3.0 in tenths, computed from
x^(1/gamma) over the video range.  host/run.sh checks the curves.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-10-03 12:09:08 +02:00
1 parent 1d34b301b8
commit f5d2119c00
8 files changed
+182 -2

No files matched your search

+6
View File
@@ -317,7 +317,13 @@ static void viewport_resize(gekko_video_t *gx, unsigned overscan_top,
unsigned gamma = settings->uints.video_gamma;
int x = 0, y = 0;
#ifdef HW_RVL
/* The A/V encoder: gamma in tenths, 0 standing for 1.0. */
gk_vi_set_trap_filter(settings->bools.video_soft_filter);
gk_vi_set_gamma(gamma ? gamma : 10);
#else
gk_gx_copy_gamma(MIN(2, gamma));
#endif
/* Below 192 lines is a custom mode: as it is. */
if (gx->efb_lines >= 192)
+1 -1
View File
@@ -14,7 +14,7 @@ GEKKO_SRC := boot/crt0.S cpu/exception.S cpu/cache.S \
ifeq ($(GEKKO_PLATFORM),rvl)
GEKKO_SRC += rvl/ipc.c rvl/stm.c rvl/es.c rvl/conf.c rvl/ave.c rvl/sdio.c \
rvl/iobuf.c rvl/usb.c rvl/usbstorage.c rvl/bt.c rvl/wiimote.c \
rvl/net.c rvl/ir.c
rvl/net.c rvl/ir.c rvl/ave_gamma.c
endif
GEKKO_SOURCES := $(addprefix $(GEKKO_DIR)/,$(GEKKO_SRC))
+8
View File
@@ -73,4 +73,12 @@ int gk_vi_field(void);
typedef void (*gk_vi_fn)(uint32_t retrace, void *data);
void gk_vi_set_retrace_cb(gk_vi_fn fn, void *data);
#if GK_RVL
/* The A/V encoder: the composite video trap filter (better luma and
* chroma separation, a softer picture), and the output gamma in
* tenths, 10 being 1.0, from 1 to 30. Both stay across modes. */
void gk_vi_set_trap_filter(int on);
void gk_vi_set_gamma(unsigned tenths);
#endif
#endif
+32
View File
@@ -2,8 +2,11 @@
* lines the processor owns. Register map and bus timing from
* WiiBrew's Hardware/AV Encoder page. */
#include <gekko/thread.h>
#include <gekko/video.h>
#include "ave_gamma.h"
#define GPIOB_OUT 0xcd8000c0u
#define GPIOB_DIR 0xcd8000c4u
#define GPIOB_IN 0xcd8000c8u
@@ -13,8 +16,13 @@
#define AVE_ADDR 0xe0
#define AVE_OUTPUT_CONFIG 0x01 /* colour encoding, YUV (component) */
#define AVE_TRAP_FILTER 0x03
#define AVE_GAMMA 0x10 /* AVE_GAMMA_LEN bytes */
#define AVE_RGB_FILTER 0x6e /* on for EURGB60 */
static gk_mutex_t lock;
static uint8_t gamma_tenths; /* 0: as the encoder was found */
static void udelay(unsigned us)
{
uint64_t end = gk_ticks() + GK_US_TO_TICKS(us);
@@ -105,6 +113,30 @@ void gk_ave_set(unsigned std, int component)
enc = 0;
break;
}
gk_mutex_lock(&lock);
write8(AVE_OUTPUT_CONFIG, (uint8_t)(enc | (component ? 0x20 : 0)));
write8(AVE_RGB_FILTER, std == GK_VI_EURGB60 ? 1 : 0);
gk_mutex_unlock(&lock);
}
void gk_vi_set_trap_filter(int on)
{
gk_mutex_lock(&lock);
write8(AVE_TRAP_FILTER, on ? 1 : 0);
gk_mutex_unlock(&lock);
}
void gk_vi_set_gamma(unsigned tenths)
{
uint8_t t[AVE_GAMMA_LEN];
tenths = tenths < 1 ? 1 : tenths > 30 ? 30 : tenths;
gk_mutex_lock(&lock);
if (tenths != gamma_tenths)
{
gamma_tenths = (uint8_t)tenths;
ave_gamma_table(tenths, t);
write_reg(AVE_GAMMA, t, sizeof(t));
}
gk_mutex_unlock(&lock);
}
+47
View File
@@ -0,0 +1,47 @@
/* The A/V encoder's gamma curve: out = in ^ (1 / gamma) over the
* video range, as straight segments between fixed knees. */
#include <math.h>
#include "ave_gamma.h"
static const uint8_t knee[7] = { 16, 32, 64, 96, 128, 160, 235 };
static double curve(double v, double gamma)
{
return 16.0 + 219.0 * pow((v - 16.0) / 219.0, 1.0 / gamma);
}
static unsigned fixed(double v, double one)
{
double f = v * one + 0.5;
return f >= 65535.0 ? 65535u : f <= 0.0 ? 0u : (unsigned)f;
}
void ave_gamma_table(unsigned tenths, uint8_t out[AVE_GAMMA_LEN])
{
double y[7];
double gamma;
unsigned i;
if (tenths < 1)
tenths = 1;
else if (tenths > 30)
tenths = 30;
gamma = tenths / 10.0;
for (i = 0; i < 7; i++)
y[i] = curve(knee[i], gamma);
for (i = 0; i < 6; i++)
{
unsigned s = fixed((y[i + 1] - y[i]) / (knee[i + 1] - knee[i]),
4096.0);
out[i * 2] = (uint8_t)(s >> 8);
out[i * 2 + 1] = (uint8_t)s;
}
for (i = 0; i < 7; i++)
{
unsigned o = fixed(y[i], 256.0);
out[12 + i] = knee[i];
out[19 + i * 2] = (uint8_t)(o >> 8);
out[19 + i * 2 + 1] = (uint8_t)o;
}
}
+15
View File
@@ -0,0 +1,15 @@
/* The A/V encoder's gamma curve, registers 0x10-0x30: six segment
* slopes (4.12 fixed point), seven input knees and the outputs at
* them (8.8 fixed point), big endian, over the 16-235 video range. */
#ifndef GEKKO_RVL_AVE_GAMMA_H
#define GEKKO_RVL_AVE_GAMMA_H
#include <stdint.h>
#define AVE_GAMMA_LEN 33
/* gamma in tenths: 10 is 1.0, the linear curve; 1 to 30. */
void ave_gamma_table(unsigned tenths, uint8_t out[AVE_GAMMA_LEN]);
#endif
+68
View File
@@ -0,0 +1,68 @@
/* rvl/ave_gamma.c: gamma 1.0 is the encoder's linear default, and
* every curve rises through the video range with slopes matching its
* outputs. */
#include <stdio.h>
#include "../../rvl/ave_gamma.h"
static int failures;
#define CHECK(cond) do { if (!(cond)) { \
printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
failures++; } } while (0)
static const uint8_t linear[AVE_GAMMA_LEN] = {
0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,
0x10, 0x20, 0x40, 0x60, 0x80, 0xa0, 0xeb,
0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x60, 0x00, 0x80, 0x00, 0xa0, 0x00,
0xeb, 0x00 };
static unsigned be16(const uint8_t *p)
{
return (unsigned)p[0] << 8 | p[1];
}
int main(void)
{
uint8_t t[AVE_GAMMA_LEN], lo[AVE_GAMMA_LEN], hi[AVE_GAMMA_LEN];
unsigned g, i;
ave_gamma_table(10, t);
for (i = 0; i < AVE_GAMMA_LEN; i++)
CHECK(t[i] == linear[i]);
for (g = 1; g <= 30; g++)
{
ave_gamma_table(g, t);
CHECK(be16(t + 19) == 16 * 256 && be16(t + 31) == 235 * 256);
for (i = 0; i < 6; i++)
{
unsigned y0 = be16(t + 19 + i * 2), y1 = be16(t + 21 + i * 2);
unsigned dx = t[13 + i] - t[12 + i];
/* The slope reaches the next output to within rounding. */
unsigned long reach = (unsigned long)y0
+ (unsigned long)be16(t + i * 2) * dx / 16;
CHECK(y1 >= y0);
CHECK(reach + dx >= y1 && reach <= y1 + dx);
}
}
/* Higher gamma lifts the darks. */
ave_gamma_table(5, lo);
ave_gamma_table(20, hi);
for (i = 1; i < 6; i++)
CHECK(be16(lo + 19 + i * 2) < be16(hi + 19 + i * 2));
/* Out of range clamps. */
ave_gamma_table(0, lo);
ave_gamma_table(1, hi);
for (i = 0; i < AVE_GAMMA_LEN; i++)
CHECK(lo[i] == hi[i]);
ave_gamma_table(99, lo);
ave_gamma_table(30, hi);
for (i = 0; i < AVE_GAMMA_LEN; i++)
CHECK(lo[i] == hi[i]);
printf("ave_gamma: %s\n", failures ? "FAILED" : "ok");
return failures ? 1 : 0;
}
+5 -1
View File
@@ -1,7 +1,8 @@
#!/bin/sh
# fs/fat.c against images made and checked by dosfstools and mtools,
# and disk/sdspi.c and disk/usbmsc.c against a model SD card and USB
# drive, kernel/exec_image.c's program layouts and rvl/ir.c's pointer.
# drive, kernel/exec_image.c's program layouts, rvl/ir.c's pointer and
# rvl/ave_gamma.c's curves.
# Needs a C compiler, mkfs.fat, fsck.fat, mtools and python3.
set -e
cd "$(dirname "$0")"
@@ -22,10 +23,13 @@ ${CC:-cc} -O1 -g -Wall -Wextra -fsanitize=address,undefined \
../../kernel/exec_image.c
${CC:-cc} -O1 -g -Wall -Wextra -fsanitize=address,undefined \
-o "$work/ir_test" ir_test.c ../../rvl/ir.c -lm
${CC:-cc} -O1 -g -Wall -Wextra -fsanitize=address,undefined \
-o "$work/ave_gamma_test" ave_gamma_test.c ../../rvl/ave_gamma.c -lm
"$work/sdspi_test"
"$work/usbmsc_test"
"$work/exec_image_test"
"$work/ir_test"
"$work/ave_gamma_test"
pattern() { # seed size file
python3 -c "import sys